@morscherlab/mld-sdk 0.6.0 → 0.6.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.
- package/dist/components/AuditTrail.vue.d.ts +1 -10
- package/dist/components/AuditTrail.vue.js.map +1 -1
- package/dist/components/BatchProgressList.vue.d.ts +1 -17
- package/dist/components/BatchProgressList.vue.js.map +1 -1
- package/dist/components/Breadcrumb.vue.d.ts +1 -5
- package/dist/components/Breadcrumb.vue.js.map +1 -1
- package/dist/components/DateTimePicker.vue.js +0 -1
- package/dist/components/DateTimePicker.vue.js.map +1 -1
- package/dist/components/MoleculeInput.vue.d.ts +1 -4
- package/dist/components/MoleculeInput.vue.js.map +1 -1
- package/dist/components/RackEditor.vue.js +2 -2
- package/dist/components/RackEditor.vue.js.map +1 -1
- package/dist/components/ReagentList.vue.d.ts +1 -15
- package/dist/components/ReagentList.vue.js.map +1 -1
- package/dist/components/SampleHierarchyTree.vue.d.ts +1 -12
- package/dist/components/SampleHierarchyTree.vue.js.map +1 -1
- package/dist/components/ScheduleCalendar.vue.js.map +1 -1
- package/dist/components/ScientificNumber.vue.d.ts +1 -1
- package/dist/components/ScientificNumber.vue.js.map +1 -1
- package/dist/components/SettingsModal.vue.d.ts +1 -5
- package/dist/components/SettingsModal.vue.js.map +1 -1
- package/dist/components/StepWizard.vue.d.ts +1 -8
- package/dist/components/StepWizard.vue.js.map +1 -1
- package/dist/components/UnitInput.vue.d.ts +1 -6
- package/dist/components/UnitInput.vue.js.map +1 -1
- package/dist/components/WellPlate.vue.d.ts +5 -1
- package/dist/components/WellPlate.vue.js +219 -27
- package/dist/components/WellPlate.vue.js.map +1 -1
- package/dist/styles.css +113 -88
- package/dist/types/components.d.ts +12 -0
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/AuditTrail.vue +1 -12
- package/src/components/BatchProgressList.vue +1 -20
- package/src/components/Breadcrumb.vue +1 -5
- package/src/components/DateTimePicker.vue +1 -1
- package/src/components/MoleculeInput.story.vue +1 -1
- package/src/components/MoleculeInput.vue +1 -5
- package/src/components/RackEditor.vue +2 -2
- package/src/components/ReagentList.story.vue +1 -1
- package/src/components/ReagentList.vue +1 -26
- package/src/components/SampleHierarchyTree.story.vue +1 -1
- package/src/components/SampleHierarchyTree.vue +1 -25
- package/src/components/ScheduleCalendar.vue +1 -1
- package/src/components/ScientificNumber.story.vue +1 -2
- package/src/components/ScientificNumber.vue +1 -2
- package/src/components/SettingsModal.vue +1 -7
- package/src/components/StepWizard.vue +1 -10
- package/src/components/UnitInput.vue +1 -7
- package/src/components/WellPlate.story.vue +85 -1
- package/src/components/WellPlate.vue +194 -9
- package/src/styles/components/button.css +2 -0
- package/src/styles/components/datetime-picker.css +4 -0
- package/src/styles/components/rack-editor.css +6 -0
- package/src/styles/components/well-plate.css +50 -57
- package/src/types/components.ts +16 -0
- package/src/types/index.ts +13 -0
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
interface AuditEntry {
|
|
3
|
-
id: string;
|
|
4
|
-
type: AuditEntryType;
|
|
5
|
-
action: string;
|
|
6
|
-
detail?: string;
|
|
7
|
-
user?: string;
|
|
8
|
-
timestamp: Date | string;
|
|
9
|
-
metadata?: Record<string, unknown>;
|
|
10
|
-
}
|
|
1
|
+
import { AuditEntry } from '../types';
|
|
11
2
|
interface Props {
|
|
12
3
|
entries: AuditEntry[];
|
|
13
4
|
maxHeight?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AuditTrail.vue.js","sources":["../../src/components/AuditTrail.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\
|
|
1
|
+
{"version":3,"file":"AuditTrail.vue.js","sources":["../../src/components/AuditTrail.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport type { AuditEntryType, AuditEntry } from '../types'\n\ninterface Props {\n entries: AuditEntry[]\n maxHeight?: string\n showFilters?: boolean\n emptyMessage?: string\n order?: 'newest' | 'oldest'\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showFilters: false,\n emptyMessage: 'No activity yet',\n order: 'newest',\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'entry-click': [entry: AuditEntry]\n}>()\n\nconst filterType = ref<AuditEntryType | ''>('')\nconst filterUser = ref('')\n\nconst uniqueTypes = computed(() => {\n const types = new Set(props.entries.map(e => e.type))\n return Array.from(types)\n})\n\nconst uniqueUsers = computed(() => {\n const users = new Set(props.entries.filter(e => e.user).map(e => e.user!))\n return Array.from(users)\n})\n\nconst filteredEntries = computed(() => {\n let result = props.entries\n if (filterType.value) {\n result = result.filter(e => e.type === filterType.value)\n }\n if (filterUser.value) {\n result = result.filter(e => e.user === filterUser.value)\n }\n return result\n})\n\nconst sortedEntries = computed(() => {\n const entries = [...filteredEntries.value]\n entries.sort((a, b) => {\n const timeA = new Date(a.timestamp).getTime()\n const timeB = new Date(b.timestamp).getTime()\n return props.order === 'newest' ? timeB - timeA : timeA - timeB\n })\n return entries\n})\n\nfunction getInitials(name: string): string {\n const words = name.trim().split(/\\s+/)\n if (words.length >= 2) {\n return (words[0][0] + words[1][0]).toUpperCase()\n }\n return (words[0]?.[0] ?? '').toUpperCase()\n}\n\nconst rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })\n\nfunction formatTimestamp(timestamp: Date | string): string {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp)\n const now = Date.now()\n const diffMs = date.getTime() - now\n const diffSec = Math.round(diffMs / 1000)\n const diffMin = Math.round(diffSec / 60)\n const diffHr = Math.round(diffMin / 60)\n const diffDay = Math.round(diffHr / 24)\n const diffWeek = Math.round(diffDay / 7)\n\n if (Math.abs(diffSec) < 60) return rtf.format(diffSec, 'second')\n if (Math.abs(diffMin) < 60) return rtf.format(diffMin, 'minute')\n if (Math.abs(diffHr) < 24) return rtf.format(diffHr, 'hour')\n if (Math.abs(diffDay) < 7) return rtf.format(diffDay, 'day')\n if (Math.abs(diffWeek) < 5) return rtf.format(diffWeek, 'week')\n return date.toLocaleDateString()\n}\n\nfunction handleEntryClick(entry: AuditEntry) {\n emit('entry-click', entry)\n}\n</script>\n\n<template>\n <div :class=\"['mld-audit-trail', `mld-audit-trail--${size}`]\">\n <div v-if=\"showFilters\" class=\"mld-audit-trail__filters\">\n <select v-model=\"filterType\" class=\"mld-audit-trail__filter-select\">\n <option value=\"\">All types</option>\n <option v-for=\"t in uniqueTypes\" :key=\"t\" :value=\"t\">{{ t }}</option>\n </select>\n <select v-model=\"filterUser\" class=\"mld-audit-trail__filter-select\">\n <option value=\"\">All users</option>\n <option v-for=\"u in uniqueUsers\" :key=\"u\" :value=\"u\">{{ u }}</option>\n </select>\n </div>\n\n <div\n v-if=\"sortedEntries.length\"\n class=\"mld-audit-trail__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div class=\"mld-audit-trail__line\" />\n <div\n v-for=\"entry in sortedEntries\"\n :key=\"entry.id\"\n :class=\"['mld-audit-trail__entry', `mld-audit-trail__entry--${entry.type}`]\"\n @click=\"handleEntryClick(entry)\"\n >\n <slot name=\"entry\" :entry=\"entry\">\n <div class=\"mld-audit-trail__dot\" />\n <div class=\"mld-audit-trail__content\">\n <div class=\"mld-audit-trail__header\">\n <div v-if=\"entry.user\" class=\"mld-audit-trail__avatar\">{{ getInitials(entry.user) }}</div>\n <span v-if=\"entry.user\" class=\"mld-audit-trail__user\">{{ entry.user }}</span>\n <span class=\"mld-audit-trail__timestamp\">{{ formatTimestamp(entry.timestamp) }}</span>\n </div>\n <div class=\"mld-audit-trail__action\">{{ entry.action }}</div>\n <div v-if=\"entry.detail\" class=\"mld-audit-trail__detail\">{{ entry.detail }}</div>\n <div v-if=\"entry.metadata && Object.keys(entry.metadata).length\" class=\"mld-audit-trail__metadata\">\n <span\n v-for=\"(value, key) in entry.metadata\"\n :key=\"String(key)\"\n class=\"mld-audit-trail__metadata-item\"\n >\n <span class=\"mld-audit-trail__metadata-key\">{{ key }}:</span>\n {{ value }}\n </span>\n </div>\n </div>\n </slot>\n </div>\n </div>\n\n <div v-if=\"!sortedEntries.length\" class=\"mld-audit-trail__empty\">\n <slot name=\"empty\">{{ emptyMessage }}</slot>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/audit-trail.css';\n</style>\n"],"names":["_createElementBlock","_openBlock","_createElementVNode","_Fragment","_renderList","_normalizeStyle","_normalizeClass","_renderSlot","_toDisplayString","_createTextVNode"],"mappings":"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"}
|
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
interface BatchItem {
|
|
3
|
-
id: string;
|
|
4
|
-
label: string;
|
|
5
|
-
status: BatchItemStatus;
|
|
6
|
-
progress?: number;
|
|
7
|
-
message?: string;
|
|
8
|
-
}
|
|
9
|
-
interface BatchSummary {
|
|
10
|
-
total: number;
|
|
11
|
-
completed: number;
|
|
12
|
-
processing: number;
|
|
13
|
-
error: number;
|
|
14
|
-
pending: number;
|
|
15
|
-
skipped: number;
|
|
16
|
-
percent: number;
|
|
17
|
-
}
|
|
1
|
+
import { BatchItem, BatchSummary } from '../types';
|
|
18
2
|
interface Props {
|
|
19
3
|
items: BatchItem[];
|
|
20
4
|
showSummary?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BatchProgressList.vue.js","sources":["../../src/components/BatchProgressList.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\n\ntype BatchItemStatus = 'pending' | 'processing' | 'completed' | 'error' | 'skipped'\n\ninterface BatchItem {\n id: string\n label: string\n status: BatchItemStatus\n progress?: number\n message?: string\n}\n\ninterface BatchSummary {\n total: number\n completed: number\n processing: number\n error: number\n pending: number\n skipped: number\n percent: number\n}\n\ninterface Props {\n items: BatchItem[]\n showSummary?: boolean\n title?: string\n maxHeight?: string\n autoScroll?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSummary: true,\n autoScroll: true,\n})\n\nconst emit = defineEmits<{\n 'retry': [id: string]\n 'cancel': [id: string]\n}>()\n\nconst listRef = ref<HTMLElement | null>(null)\nconst expandedErrors = ref<Set<string>>(new Set())\n\nconst summary = computed<BatchSummary>(() => {\n const total = props.items.length\n const completed = props.items.filter(i => i.status === 'completed').length\n const processing = props.items.filter(i => i.status === 'processing').length\n const error = props.items.filter(i => i.status === 'error').length\n const pending = props.items.filter(i => i.status === 'pending').length\n const skipped = props.items.filter(i => i.status === 'skipped').length\n const percent = total > 0 ? Math.round(((completed + skipped) / total) * 100) : 0\n return { total, completed, processing, error, pending, skipped, percent }\n})\n\nfunction toggleError(id: string) {\n if (expandedErrors.value.has(id)) {\n expandedErrors.value.delete(id)\n } else {\n expandedErrors.value.add(id)\n }\n}\n\nwatch(\n () => props.items.map(i => i.status),\n (newStatuses, oldStatuses) => {\n if (!props.autoScroll || !listRef.value) return\n for (let i = 0; i < newStatuses.length; i++) {\n if (newStatuses[i] === 'processing' && oldStatuses?.[i] !== 'processing') {\n nextTick(() => {\n const el = listRef.value?.querySelector(`[data-item-id=\"${props.items[i].id}\"]`)\n el?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n })\n break\n }\n }\n },\n)\n</script>\n\n<template>\n <div class=\"mld-batch-progress\">\n <div v-if=\"title\" class=\"mld-batch-progress__header\">\n <span class=\"mld-batch-progress__title\">{{ title }}</span>\n <span class=\"mld-batch-progress__percent\">{{ summary.percent }}%</span>\n </div>\n\n <div class=\"mld-batch-progress__overall\">\n <div\n class=\"mld-batch-progress__overall-bar\"\n :style=\"{ width: `${summary.percent}%` }\"\n />\n </div>\n\n <slot v-if=\"showSummary\" name=\"summary\" :summary=\"summary\">\n <div class=\"mld-batch-progress__summary\">\n <span v-if=\"summary.completed\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--completed\">\n {{ summary.completed }} completed\n </span>\n <span v-if=\"summary.processing\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--processing\">\n {{ summary.processing }} processing\n </span>\n <span v-if=\"summary.error\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--error\">\n {{ summary.error }} failed\n </span>\n <span v-if=\"summary.pending\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--pending\">\n {{ summary.pending }} pending\n </span>\n <span v-if=\"summary.skipped\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--skipped\">\n {{ summary.skipped }} skipped\n </span>\n </div>\n </slot>\n\n <div\n ref=\"listRef\"\n class=\"mld-batch-progress__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n :data-item-id=\"item.id\"\n :class=\"['mld-batch-progress__item', `mld-batch-progress__item--${item.status}`]\"\n >\n <slot name=\"item\" :item=\"item\">\n <div class=\"mld-batch-progress__item-row\">\n <!-- Status icon -->\n <div :class=\"['mld-batch-progress__item-icon', `mld-batch-progress__item-icon--${item.status}`]\">\n <!-- Pending: clock -->\n <svg v-if=\"item.status === 'pending'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"8\" cy=\"8\" r=\"6.5\" />\n <path d=\"M8 4.5V8l2.5 1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Processing: spinner -->\n <svg v-else-if=\"item.status === 'processing'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M8 1.5A6.5 6.5 0 1 1 1.5 8\" stroke-linecap=\"round\" />\n </svg>\n <!-- Completed: checkmark -->\n <svg v-else-if=\"item.status === 'completed'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M3.5 8.5L6.5 11.5L12.5 4.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Error: x -->\n <svg v-else-if=\"item.status === 'error'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4.5 4.5l7 7M11.5 4.5l-7 7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Skipped: dash -->\n <svg v-else viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 8h8\" stroke-linecap=\"round\" />\n </svg>\n </div>\n\n <span class=\"mld-batch-progress__item-label\">{{ item.label }}</span>\n\n <!-- Mini progress bar for processing items -->\n <div v-if=\"item.status === 'processing' && item.progress !== undefined\" class=\"mld-batch-progress__item-progress\">\n <div class=\"mld-batch-progress__item-progress-bar\" :style=\"{ width: `${item.progress}%` }\" />\n </div>\n\n <!-- Actions -->\n <div class=\"mld-batch-progress__item-actions\">\n <button\n v-if=\"item.status === 'error'\"\n type=\"button\"\n class=\"mld-batch-progress__retry-btn\"\n @click.stop=\"emit('retry', item.id)\"\n >\n Retry\n </button>\n <button\n v-if=\"item.status === 'processing'\"\n type=\"button\"\n class=\"mld-batch-progress__cancel-btn\"\n @click.stop=\"emit('cancel', item.id)\"\n >\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Error message (expandable) -->\n <div v-if=\"item.status === 'error' && item.message\" class=\"mld-batch-progress__item-error\">\n <button\n type=\"button\"\n class=\"mld-batch-progress__error-toggle\"\n @click.stop=\"toggleError(item.id)\"\n >\n {{ expandedErrors.has(item.id) ? 'Hide error' : 'Show error' }}\n </button>\n <div v-if=\"expandedErrors.has(item.id)\" class=\"mld-batch-progress__item-message\">\n {{ item.message }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/batch-progress-list.css';\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_normalizeStyle","_renderSlot","_Fragment","_renderList","_normalizeClass","_withModifiers"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"BatchProgressList.vue.js","sources":["../../src/components/BatchProgressList.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch, nextTick } from 'vue'\nimport type { BatchItem, BatchSummary } from '../types'\n\ninterface Props {\n items: BatchItem[]\n showSummary?: boolean\n title?: string\n maxHeight?: string\n autoScroll?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showSummary: true,\n autoScroll: true,\n})\n\nconst emit = defineEmits<{\n 'retry': [id: string]\n 'cancel': [id: string]\n}>()\n\nconst listRef = ref<HTMLElement | null>(null)\nconst expandedErrors = ref<Set<string>>(new Set())\n\nconst summary = computed<BatchSummary>(() => {\n const total = props.items.length\n const completed = props.items.filter(i => i.status === 'completed').length\n const processing = props.items.filter(i => i.status === 'processing').length\n const error = props.items.filter(i => i.status === 'error').length\n const pending = props.items.filter(i => i.status === 'pending').length\n const skipped = props.items.filter(i => i.status === 'skipped').length\n const percent = total > 0 ? Math.round(((completed + skipped) / total) * 100) : 0\n return { total, completed, processing, error, pending, skipped, percent }\n})\n\nfunction toggleError(id: string) {\n if (expandedErrors.value.has(id)) {\n expandedErrors.value.delete(id)\n } else {\n expandedErrors.value.add(id)\n }\n}\n\nwatch(\n () => props.items.map(i => i.status),\n (newStatuses, oldStatuses) => {\n if (!props.autoScroll || !listRef.value) return\n for (let i = 0; i < newStatuses.length; i++) {\n if (newStatuses[i] === 'processing' && oldStatuses?.[i] !== 'processing') {\n nextTick(() => {\n const el = listRef.value?.querySelector(`[data-item-id=\"${props.items[i].id}\"]`)\n el?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n })\n break\n }\n }\n },\n)\n</script>\n\n<template>\n <div class=\"mld-batch-progress\">\n <div v-if=\"title\" class=\"mld-batch-progress__header\">\n <span class=\"mld-batch-progress__title\">{{ title }}</span>\n <span class=\"mld-batch-progress__percent\">{{ summary.percent }}%</span>\n </div>\n\n <div class=\"mld-batch-progress__overall\">\n <div\n class=\"mld-batch-progress__overall-bar\"\n :style=\"{ width: `${summary.percent}%` }\"\n />\n </div>\n\n <slot v-if=\"showSummary\" name=\"summary\" :summary=\"summary\">\n <div class=\"mld-batch-progress__summary\">\n <span v-if=\"summary.completed\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--completed\">\n {{ summary.completed }} completed\n </span>\n <span v-if=\"summary.processing\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--processing\">\n {{ summary.processing }} processing\n </span>\n <span v-if=\"summary.error\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--error\">\n {{ summary.error }} failed\n </span>\n <span v-if=\"summary.pending\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--pending\">\n {{ summary.pending }} pending\n </span>\n <span v-if=\"summary.skipped\" class=\"mld-batch-progress__summary-item mld-batch-progress__summary-item--skipped\">\n {{ summary.skipped }} skipped\n </span>\n </div>\n </slot>\n\n <div\n ref=\"listRef\"\n class=\"mld-batch-progress__list\"\n :style=\"maxHeight ? { maxHeight, overflowY: 'auto' } : {}\"\n >\n <div\n v-for=\"item in items\"\n :key=\"item.id\"\n :data-item-id=\"item.id\"\n :class=\"['mld-batch-progress__item', `mld-batch-progress__item--${item.status}`]\"\n >\n <slot name=\"item\" :item=\"item\">\n <div class=\"mld-batch-progress__item-row\">\n <!-- Status icon -->\n <div :class=\"['mld-batch-progress__item-icon', `mld-batch-progress__item-icon--${item.status}`]\">\n <!-- Pending: clock -->\n <svg v-if=\"item.status === 'pending'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <circle cx=\"8\" cy=\"8\" r=\"6.5\" />\n <path d=\"M8 4.5V8l2.5 1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Processing: spinner -->\n <svg v-else-if=\"item.status === 'processing'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M8 1.5A6.5 6.5 0 1 1 1.5 8\" stroke-linecap=\"round\" />\n </svg>\n <!-- Completed: checkmark -->\n <svg v-else-if=\"item.status === 'completed'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M3.5 8.5L6.5 11.5L12.5 4.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Error: x -->\n <svg v-else-if=\"item.status === 'error'\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4.5 4.5l7 7M11.5 4.5l-7 7\" stroke-linecap=\"round\" stroke-linejoin=\"round\" />\n </svg>\n <!-- Skipped: dash -->\n <svg v-else viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M4 8h8\" stroke-linecap=\"round\" />\n </svg>\n </div>\n\n <span class=\"mld-batch-progress__item-label\">{{ item.label }}</span>\n\n <!-- Mini progress bar for processing items -->\n <div v-if=\"item.status === 'processing' && item.progress !== undefined\" class=\"mld-batch-progress__item-progress\">\n <div class=\"mld-batch-progress__item-progress-bar\" :style=\"{ width: `${item.progress}%` }\" />\n </div>\n\n <!-- Actions -->\n <div class=\"mld-batch-progress__item-actions\">\n <button\n v-if=\"item.status === 'error'\"\n type=\"button\"\n class=\"mld-batch-progress__retry-btn\"\n @click.stop=\"emit('retry', item.id)\"\n >\n Retry\n </button>\n <button\n v-if=\"item.status === 'processing'\"\n type=\"button\"\n class=\"mld-batch-progress__cancel-btn\"\n @click.stop=\"emit('cancel', item.id)\"\n >\n Cancel\n </button>\n </div>\n </div>\n\n <!-- Error message (expandable) -->\n <div v-if=\"item.status === 'error' && item.message\" class=\"mld-batch-progress__item-error\">\n <button\n type=\"button\"\n class=\"mld-batch-progress__error-toggle\"\n @click.stop=\"toggleError(item.id)\"\n >\n {{ expandedErrors.has(item.id) ? 'Hide error' : 'Show error' }}\n </button>\n <div v-if=\"expandedErrors.has(item.id)\" class=\"mld-batch-progress__item-message\">\n {{ item.message }}\n </div>\n </div>\n </slot>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/batch-progress-list.css';\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_normalizeStyle","_renderSlot","_Fragment","_renderList","_normalizeClass","_withModifiers"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Breadcrumb.vue.js","sources":["../../src/components/Breadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\
|
|
1
|
+
{"version":3,"file":"Breadcrumb.vue.js","sources":["../../src/components/Breadcrumb.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { BreadcrumbItem } from '../types'\n\ninterface Props {\n items: BreadcrumbItem[]\n separator?: string\n}\n\nwithDefaults(defineProps<Props>(), {\n separator: '/',\n})\n\nconst emit = defineEmits<{\n navigate: [item: BreadcrumbItem]\n}>()\n\nfunction handleClick(item: BreadcrumbItem) {\n if (!item.href) {\n emit('navigate', item)\n }\n}\n</script>\n\n<template>\n <nav class=\"mld-breadcrumb\" aria-label=\"Breadcrumb\">\n <ol class=\"mld-breadcrumb__list\">\n <li\n v-for=\"(item, index) in items\"\n :key=\"index\"\n class=\"mld-breadcrumb__item\"\n >\n <slot name=\"item\" :item=\"item\" :index=\"index\" :is-last=\"index === items.length - 1\">\n <a\n v-if=\"item.href && index !== items.length - 1\"\n :href=\"item.href\"\n class=\"mld-breadcrumb__link\"\n >\n {{ item.label }}\n </a>\n <button\n v-else-if=\"index !== items.length - 1\"\n class=\"mld-breadcrumb__link\"\n @click=\"handleClick(item)\"\n >\n {{ item.label }}\n </button>\n <span\n v-else\n class=\"mld-breadcrumb__current\"\n aria-current=\"page\"\n >\n {{ item.label }}\n </span>\n </slot>\n <span\n v-if=\"index !== items.length - 1\"\n class=\"mld-breadcrumb__separator\"\n aria-hidden=\"true\"\n >\n <slot name=\"separator\">{{ separator }}</slot>\n </span>\n </li>\n </ol>\n </nav>\n</template>\n\n<style>\n@import '../styles/components/breadcrumb.css';\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_Fragment","_renderList","_renderSlot","_toDisplayString"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAYA,UAAM,OAAO;AAIb,aAAS,YAAY,MAAsB;AACzC,UAAI,CAAC,KAAK,MAAM;AACd,aAAK,YAAY,IAAI;AAAA,MACvB;AAAA,IACF;;AAIE,aAAAA,UAAA,GAAAC,mBAuCM,OAvCN,YAuCM;AAAA,QAtCJC,mBAqCK,MArCL,YAqCK;AAAA,WApCHF,UAAA,IAAA,GAAAC,mBAmCKE,UAAA,MAAAC,WAlCqB,QAAA,OAAK,CAArB,MAAM,UAAK;gCADrBH,mBAmCK,MAAA;AAAA,cAjCF,KAAK;AAAA,cACN,OAAM;AAAA,YAAA;cAENI,WAsBO,KAAA,QAAA,QAAA;AAAA,gBAtBY;AAAA,gBAAa;AAAA,gBAAe,QAAS,UAAU,QAAA,MAAM,SAAM;AAAA,cAAA,GAA9E,MAsBO;AAAA,gBApBG,KAAK,QAAQ,UAAU,QAAA,MAAM,SAAM,kBAD3CJ,mBAMI,KAAA;AAAA;kBAJD,MAAM,KAAK;AAAA,kBACZ,OAAM;AAAA,gBAAA,GAEHK,gBAAA,KAAK,KAAK,GAAA,GAAA,UAAA,KAGF,UAAU,QAAA,MAAM,SAAM,kBADnCL,mBAMS,UAAA;AAAA;kBAJP,OAAM;AAAA,kBACL,SAAK,CAAA,WAAE,YAAY,IAAI;AAAA,gBAAA,GAErBK,gBAAA,KAAK,KAAK,GAAA,GAAA,UAAA,MAEfN,UAAA,GAAAC,mBAMO,QANP,YAMOK,gBADF,KAAK,KAAK,GAAA,CAAA;AAAA,cAAA;cAIT,UAAU,QAAA,MAAM,SAAM,KAD9BN,aAAAC,mBAMO,QANP,YAMO;AAAA,gBADLI,WAA6C,8BAA7C,MAA6C;AAAA,kDAAnB,QAAA,SAAS,GAAA,CAAA;AAAA,gBAAA;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateTimePicker.vue.js","sources":["../../src/components/DateTimePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'\nimport { generateTimeSlots, formatTime, parseTime } from '../composables/useTimeUtils'\n\ninterface Props {\n modelValue?: string\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n min?: string\n max?: string\n timeStep?: number\n timeFormat?: '12h' | '24h'\n clearable?: boolean\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Select date & time',\n disabled: false,\n error: false,\n size: 'md',\n timeStep: 15,\n timeFormat: '24h',\n clearable: false,\n locale: 'en-US',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | undefined]\n}>()\n\nconst isOpen = ref(false)\nconst containerRef = ref<HTMLDivElement>()\n\n// Parse model value into date and time parts\nconst selectedDate = computed(() => {\n if (!props.modelValue) return null\n const d = new Date(props.modelValue)\n return isNaN(d.getTime()) ? null : d\n})\n\nconst selectedDateStr = computed(() => {\n if (!selectedDate.value) return null\n const y = selectedDate.value.getFullYear()\n const m = String(selectedDate.value.getMonth() + 1).padStart(2, '0')\n const d = String(selectedDate.value.getDate()).padStart(2, '0')\n return `${y}-${m}-${d}`\n})\n\nconst selectedTimeStr = computed(() => {\n if (!selectedDate.value) return null\n return formatTime(selectedDate.value.getHours(), selectedDate.value.getMinutes())\n})\n\nconst displayValue = computed(() => {\n if (!selectedDate.value) return ''\n const dateStr = selectedDate.value.toLocaleDateString(props.locale, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n const timeStr = formatTime(\n selectedDate.value.getHours(),\n selectedDate.value.getMinutes(),\n props.timeFormat,\n )\n return `${dateStr} ${timeStr}`\n})\n\n// Calendar state\nconst currentMonth = ref(new Date())\nconst weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']\n\nconst calendarDays = computed(() => {\n const year = currentMonth.value.getFullYear()\n const month = currentMonth.value.getMonth()\n const firstDay = new Date(year, month, 1)\n const lastDay = new Date(year, month + 1, 0)\n\n const days: { date: Date; isCurrentMonth: boolean; isDisabled: boolean }[] = []\n\n const startPadding = firstDay.getDay()\n for (let i = startPadding - 1; i >= 0; i--) {\n const date = new Date(year, month, -i)\n days.push({ date, isCurrentMonth: false, isDisabled: isDateDisabled(date) })\n }\n\n for (let i = 1; i <= lastDay.getDate(); i++) {\n const date = new Date(year, month, i)\n days.push({ date, isCurrentMonth: true, isDisabled: isDateDisabled(date) })\n }\n\n const endPadding = 42 - days.length\n for (let i = 1; i <= endPadding; i++) {\n const date = new Date(year, month + 1, i)\n days.push({ date, isCurrentMonth: false, isDisabled: isDateDisabled(date) })\n }\n\n return days\n})\n\n// Time slots\nconst timeSlots = computed(() => {\n return generateTimeSlots('00:00', '23:59', props.timeStep)\n})\n\nconst monthYear = computed(() => {\n return currentMonth.value.toLocaleDateString(props.locale, {\n year: 'numeric',\n month: 'long',\n })\n})\n\nfunction isDateDisabled(date: Date): boolean {\n if (props.min) {\n const minDate = new Date(props.min)\n minDate.setHours(0, 0, 0, 0)\n const check = new Date(date)\n check.setHours(0, 0, 0, 0)\n if (check < minDate) return true\n }\n if (props.max) {\n const maxDate = new Date(props.max)\n maxDate.setHours(23, 59, 59, 999)\n const check = new Date(date)\n check.setHours(23, 59, 59, 999)\n if (check > maxDate) return true\n }\n return false\n}\n\nfunction isTimeDisabled(time: string): boolean {\n if (!selectedDateStr.value) return false\n const { hour, minute } = parseTime(time)\n\n if (props.min) {\n const minDate = new Date(props.min)\n if (selectedDateStr.value === formatDateStr(minDate)) {\n const minMin = minDate.getHours() * 60 + minDate.getMinutes()\n if (hour * 60 + minute < minMin) return true\n }\n }\n if (props.max) {\n const maxDate = new Date(props.max)\n if (selectedDateStr.value === formatDateStr(maxDate)) {\n const maxMin = maxDate.getHours() * 60 + maxDate.getMinutes()\n if (hour * 60 + minute > maxMin) return true\n }\n }\n return false\n}\n\nfunction formatDateStr(d: Date): string {\n const y = d.getFullYear()\n const m = String(d.getMonth() + 1).padStart(2, '0')\n const day = String(d.getDate()).padStart(2, '0')\n return `${y}-${m}-${day}`\n}\n\nfunction isSameDay(a: Date, b: Date | null): boolean {\n if (!b) return false\n return a.toDateString() === b.toDateString()\n}\n\nfunction isToday(date: Date): boolean {\n return date.toDateString() === new Date().toDateString()\n}\n\nfunction selectDate(day: { date: Date; isDisabled: boolean }) {\n if (day.isDisabled || props.disabled) return\n const dateStr = formatDateStr(day.date)\n const timeStr = selectedTimeStr.value || '09:00'\n emit('update:modelValue', `${dateStr}T${timeStr}`)\n}\n\nfunction selectTime(time: string) {\n if (isTimeDisabled(time)) return\n const dateStr = selectedDateStr.value || formatDateStr(new Date())\n emit('update:modelValue', `${dateStr}T${time}`)\n}\n\nfunction prevMonth() {\n currentMonth.value = new Date(\n currentMonth.value.getFullYear(),\n currentMonth.value.getMonth() - 1,\n 1,\n )\n}\n\nfunction nextMonth() {\n currentMonth.value = new Date(\n currentMonth.value.getFullYear(),\n currentMonth.value.getMonth() + 1,\n 1,\n )\n}\n\nfunction goToNow() {\n const now = new Date()\n currentMonth.value = new Date(now.getFullYear(), now.getMonth(), 1)\n const dateStr = formatDateStr(now)\n const timeStr = formatTime(now.getHours(), now.getMinutes())\n emit('update:modelValue', `${dateStr}T${timeStr}`)\n isOpen.value = false\n}\n\nfunction goToToday() {\n const today = new Date()\n currentMonth.value = new Date(today.getFullYear(), today.getMonth(), 1)\n const dateStr = formatDateStr(today)\n const timeStr = selectedTimeStr.value || '09:00'\n emit('update:modelValue', `${dateStr}T${timeStr}`)\n}\n\nfunction clear() {\n emit('update:modelValue', undefined)\n isOpen.value = false\n}\n\nfunction toggleDropdown() {\n if (props.disabled) return\n isOpen.value = !isOpen.value\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n if (containerRef.value && !containerRef.value.contains(event.target as Node)) {\n isOpen.value = false\n }\n}\n\nwatch(isOpen, (open) => {\n if (open && selectedDate.value) {\n currentMonth.value = new Date(\n selectedDate.value.getFullYear(),\n selectedDate.value.getMonth(),\n 1,\n )\n nextTick(() => {\n // Scroll time grid to selected time\n const grid = containerRef.value?.querySelector('.mld-datetime-picker__time-grid')\n const active = containerRef.value?.querySelector('.mld-datetime-picker__time-chip--active')\n if (grid && active) {\n active.scrollIntoView({ block: 'center' })\n }\n })\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n})\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"mld-datetime-picker\">\n <div class=\"mld-datetime-picker__input-wrapper\">\n <div class=\"mld-datetime-picker__icon-calendar\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\" />\n </svg>\n </div>\n <input\n type=\"text\"\n readonly\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-datetime-picker__input',\n `mld-datetime-picker__input--${size}`,\n error ? 'mld-datetime-picker__input--error' : '',\n disabled ? 'mld-datetime-picker__input--disabled' : '',\n ]\"\n aria-label=\"Select date and time\"\n @click=\"toggleDropdown\"\n />\n <div class=\"mld-datetime-picker__icon-clock\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </div>\n </div>\n\n <Transition\n enter-active-class=\"mld-datetime-picker__dropdown-enter-active\"\n enter-from-class=\"mld-datetime-picker__dropdown-enter-from\"\n enter-to-class=\"mld-datetime-picker__dropdown-enter-to\"\n leave-active-class=\"mld-datetime-picker__dropdown-leave-active\"\n leave-from-class=\"mld-datetime-picker__dropdown-leave-from\"\n leave-to-class=\"mld-datetime-picker__dropdown-leave-to\"\n >\n <div\n v-if=\"isOpen\"\n class=\"mld-datetime-picker__dropdown\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Date and time picker\"\n >\n <!-- Calendar section -->\n <div class=\"mld-datetime-picker__calendar-section\">\n <div class=\"mld-date-picker__header\">\n <button type=\"button\" class=\"mld-date-picker__nav-btn\" aria-label=\"Previous month\" @click=\"prevMonth\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n <span class=\"mld-date-picker__month-year\">{{ monthYear }}</span>\n <button type=\"button\" class=\"mld-date-picker__nav-btn\" aria-label=\"Next month\" @click=\"nextMonth\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n\n <div class=\"mld-date-picker__weekdays\">\n <div v-for=\"day in weekDays\" :key=\"day\" class=\"mld-date-picker__weekday\">{{ day }}</div>\n </div>\n\n <div class=\"mld-date-picker__grid\">\n <button\n v-for=\"(day, index) in calendarDays\"\n :key=\"index\"\n type=\"button\"\n :disabled=\"day.isDisabled\"\n :class=\"[\n 'mld-date-picker__day',\n !day.isCurrentMonth ? 'mld-date-picker__day--other-month' : '',\n day.isDisabled ? 'mld-date-picker__day--disabled' : '',\n isSameDay(day.date, selectedDate) ? 'mld-date-picker__day--selected' : '',\n isToday(day.date) && !isSameDay(day.date, selectedDate) ? 'mld-date-picker__day--today' : '',\n ]\"\n @click=\"selectDate(day)\"\n >\n {{ day.date.getDate() }}\n </button>\n </div>\n </div>\n\n <!-- Divider -->\n <div class=\"mld-datetime-picker__divider\" />\n\n <!-- Time section -->\n <div class=\"mld-datetime-picker__time-section\">\n <div class=\"mld-datetime-picker__time-label\">Time</div>\n <div class=\"mld-datetime-picker__time-grid\">\n <button\n v-for=\"time in timeSlots\"\n :key=\"time\"\n type=\"button\"\n :disabled=\"isTimeDisabled(time)\"\n :class=\"[\n 'mld-datetime-picker__time-chip',\n selectedTimeStr === time ? 'mld-datetime-picker__time-chip--active' : '',\n isTimeDisabled(time) ? 'mld-datetime-picker__time-chip--disabled' : '',\n ]\"\n @click=\"selectTime(time)\"\n >\n {{ timeFormat === '12h' ? formatTime(parseTime(time).hour, parseTime(time).minute, '12h') : time }}\n </button>\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"mld-datetime-picker__footer\">\n <div>\n <button type=\"button\" class=\"mld-datetime-picker__footer-btn\" @click=\"goToToday\">Today</button>\n <button type=\"button\" class=\"mld-datetime-picker__footer-btn\" style=\"margin-left: 0.75rem\" @click=\"goToNow\">Now</button>\n </div>\n <button\n v-if=\"clearable && modelValue\"\n type=\"button\"\n class=\"mld-datetime-picker__footer-btn mld-datetime-picker__footer-btn--muted\"\n @click=\"clear\"\n >\n Clear\n </button>\n </div>\n </div>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/datetime-picker.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_normalizeClass","_createVNode","_Transition","_openBlock","_toDisplayString","_Fragment","_renderList","_unref"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DateTimePicker.vue.js","sources":["../../src/components/DateTimePicker.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'\nimport { generateTimeSlots, formatTime, parseTime } from '../composables/useTimeUtils'\n\ninterface Props {\n modelValue?: string\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n min?: string\n max?: string\n timeStep?: number\n timeFormat?: '12h' | '24h'\n clearable?: boolean\n locale?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Select date & time',\n disabled: false,\n error: false,\n size: 'md',\n timeStep: 15,\n timeFormat: '24h',\n clearable: false,\n locale: 'en-US',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | undefined]\n}>()\n\nconst isOpen = ref(false)\nconst containerRef = ref<HTMLDivElement>()\n\n// Parse model value into date and time parts\nconst selectedDate = computed(() => {\n if (!props.modelValue) return null\n const d = new Date(props.modelValue)\n return isNaN(d.getTime()) ? null : d\n})\n\nconst selectedDateStr = computed(() => {\n if (!selectedDate.value) return null\n const y = selectedDate.value.getFullYear()\n const m = String(selectedDate.value.getMonth() + 1).padStart(2, '0')\n const d = String(selectedDate.value.getDate()).padStart(2, '0')\n return `${y}-${m}-${d}`\n})\n\nconst selectedTimeStr = computed(() => {\n if (!selectedDate.value) return null\n return formatTime(selectedDate.value.getHours(), selectedDate.value.getMinutes())\n})\n\nconst displayValue = computed(() => {\n if (!selectedDate.value) return ''\n const dateStr = selectedDate.value.toLocaleDateString(props.locale, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n const timeStr = formatTime(\n selectedDate.value.getHours(),\n selectedDate.value.getMinutes(),\n props.timeFormat,\n )\n return `${dateStr} ${timeStr}`\n})\n\n// Calendar state\nconst currentMonth = ref(new Date())\nconst weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']\n\nconst calendarDays = computed(() => {\n const year = currentMonth.value.getFullYear()\n const month = currentMonth.value.getMonth()\n const firstDay = new Date(year, month, 1)\n const lastDay = new Date(year, month + 1, 0)\n\n const days: { date: Date; isCurrentMonth: boolean; isDisabled: boolean }[] = []\n\n const startPadding = firstDay.getDay()\n for (let i = startPadding - 1; i >= 0; i--) {\n const date = new Date(year, month, -i)\n days.push({ date, isCurrentMonth: false, isDisabled: isDateDisabled(date) })\n }\n\n for (let i = 1; i <= lastDay.getDate(); i++) {\n const date = new Date(year, month, i)\n days.push({ date, isCurrentMonth: true, isDisabled: isDateDisabled(date) })\n }\n\n const endPadding = 42 - days.length\n for (let i = 1; i <= endPadding; i++) {\n const date = new Date(year, month + 1, i)\n days.push({ date, isCurrentMonth: false, isDisabled: isDateDisabled(date) })\n }\n\n return days\n})\n\n// Time slots\nconst timeSlots = computed(() => {\n return generateTimeSlots('00:00', '23:59', props.timeStep)\n})\n\nconst monthYear = computed(() => {\n return currentMonth.value.toLocaleDateString(props.locale, {\n year: 'numeric',\n month: 'long',\n })\n})\n\nfunction isDateDisabled(date: Date): boolean {\n if (props.min) {\n const minDate = new Date(props.min)\n minDate.setHours(0, 0, 0, 0)\n const check = new Date(date)\n check.setHours(0, 0, 0, 0)\n if (check < minDate) return true\n }\n if (props.max) {\n const maxDate = new Date(props.max)\n maxDate.setHours(23, 59, 59, 999)\n const check = new Date(date)\n check.setHours(23, 59, 59, 999)\n if (check > maxDate) return true\n }\n return false\n}\n\nfunction isTimeDisabled(time: string): boolean {\n if (!selectedDateStr.value) return false\n const { hour, minute } = parseTime(time)\n\n if (props.min) {\n const minDate = new Date(props.min)\n if (selectedDateStr.value === formatDateStr(minDate)) {\n const minMin = minDate.getHours() * 60 + minDate.getMinutes()\n if (hour * 60 + minute < minMin) return true\n }\n }\n if (props.max) {\n const maxDate = new Date(props.max)\n if (selectedDateStr.value === formatDateStr(maxDate)) {\n const maxMin = maxDate.getHours() * 60 + maxDate.getMinutes()\n if (hour * 60 + minute > maxMin) return true\n }\n }\n return false\n}\n\nfunction formatDateStr(d: Date): string {\n const y = d.getFullYear()\n const m = String(d.getMonth() + 1).padStart(2, '0')\n const day = String(d.getDate()).padStart(2, '0')\n return `${y}-${m}-${day}`\n}\n\nfunction isSameDay(a: Date, b: Date | null): boolean {\n if (!b) return false\n return a.toDateString() === b.toDateString()\n}\n\nfunction isToday(date: Date): boolean {\n return date.toDateString() === new Date().toDateString()\n}\n\nfunction selectDate(day: { date: Date; isDisabled: boolean }) {\n if (day.isDisabled || props.disabled) return\n const dateStr = formatDateStr(day.date)\n const timeStr = selectedTimeStr.value || '09:00'\n emit('update:modelValue', `${dateStr}T${timeStr}`)\n}\n\nfunction selectTime(time: string) {\n if (isTimeDisabled(time)) return\n const dateStr = selectedDateStr.value || formatDateStr(new Date())\n emit('update:modelValue', `${dateStr}T${time}`)\n}\n\nfunction prevMonth() {\n currentMonth.value = new Date(\n currentMonth.value.getFullYear(),\n currentMonth.value.getMonth() - 1,\n 1,\n )\n}\n\nfunction nextMonth() {\n currentMonth.value = new Date(\n currentMonth.value.getFullYear(),\n currentMonth.value.getMonth() + 1,\n 1,\n )\n}\n\nfunction goToNow() {\n const now = new Date()\n currentMonth.value = new Date(now.getFullYear(), now.getMonth(), 1)\n const dateStr = formatDateStr(now)\n const timeStr = formatTime(now.getHours(), now.getMinutes())\n emit('update:modelValue', `${dateStr}T${timeStr}`)\n isOpen.value = false\n}\n\nfunction goToToday() {\n const today = new Date()\n currentMonth.value = new Date(today.getFullYear(), today.getMonth(), 1)\n const dateStr = formatDateStr(today)\n const timeStr = selectedTimeStr.value || '09:00'\n emit('update:modelValue', `${dateStr}T${timeStr}`)\n}\n\nfunction clear() {\n emit('update:modelValue', undefined)\n isOpen.value = false\n}\n\nfunction toggleDropdown() {\n if (props.disabled) return\n isOpen.value = !isOpen.value\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n if (containerRef.value && !containerRef.value.contains(event.target as Node)) {\n isOpen.value = false\n }\n}\n\nwatch(isOpen, (open) => {\n if (open && selectedDate.value) {\n currentMonth.value = new Date(\n selectedDate.value.getFullYear(),\n selectedDate.value.getMonth(),\n 1,\n )\n nextTick(() => {\n // Scroll time grid to selected time\n const grid = containerRef.value?.querySelector('.mld-datetime-picker__time-grid')\n const active = containerRef.value?.querySelector('.mld-datetime-picker__time-chip--active')\n if (grid && active) {\n active.scrollIntoView({ block: 'center' })\n }\n })\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n})\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"mld-datetime-picker\">\n <div class=\"mld-datetime-picker__input-wrapper\">\n <div class=\"mld-datetime-picker__icon-calendar\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z\" />\n </svg>\n </div>\n <input\n type=\"text\"\n readonly\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-datetime-picker__input',\n `mld-datetime-picker__input--${size}`,\n error ? 'mld-datetime-picker__input--error' : '',\n disabled ? 'mld-datetime-picker__input--disabled' : '',\n ]\"\n aria-label=\"Select date and time\"\n @click=\"toggleDropdown\"\n />\n <div class=\"mld-datetime-picker__icon-clock\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n </div>\n </div>\n\n <Transition\n enter-active-class=\"mld-datetime-picker__dropdown-enter-active\"\n enter-from-class=\"mld-datetime-picker__dropdown-enter-from\"\n enter-to-class=\"mld-datetime-picker__dropdown-enter-to\"\n leave-active-class=\"mld-datetime-picker__dropdown-leave-active\"\n leave-from-class=\"mld-datetime-picker__dropdown-leave-from\"\n leave-to-class=\"mld-datetime-picker__dropdown-leave-to\"\n >\n <div\n v-if=\"isOpen\"\n class=\"mld-datetime-picker__dropdown\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Date and time picker\"\n >\n <!-- Calendar section -->\n <div class=\"mld-datetime-picker__calendar-section\">\n <div class=\"mld-date-picker__header\">\n <button type=\"button\" class=\"mld-date-picker__nav-btn\" aria-label=\"Previous month\" @click=\"prevMonth\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 19l-7-7 7-7\" />\n </svg>\n </button>\n <span class=\"mld-date-picker__month-year\">{{ monthYear }}</span>\n <button type=\"button\" class=\"mld-date-picker__nav-btn\" aria-label=\"Next month\" @click=\"nextMonth\">\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\" aria-hidden=\"true\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 5l7 7-7 7\" />\n </svg>\n </button>\n </div>\n\n <div class=\"mld-date-picker__weekdays\">\n <div v-for=\"day in weekDays\" :key=\"day\" class=\"mld-date-picker__weekday\">{{ day }}</div>\n </div>\n\n <div class=\"mld-date-picker__grid\">\n <button\n v-for=\"(day, index) in calendarDays\"\n :key=\"index\"\n type=\"button\"\n :disabled=\"day.isDisabled\"\n :class=\"[\n 'mld-date-picker__day',\n !day.isCurrentMonth ? 'mld-date-picker__day--other-month' : '',\n day.isDisabled ? 'mld-date-picker__day--disabled' : '',\n isSameDay(day.date, selectedDate) ? 'mld-date-picker__day--selected' : '',\n isToday(day.date) && !isSameDay(day.date, selectedDate) ? 'mld-date-picker__day--today' : '',\n ]\"\n @click=\"selectDate(day)\"\n >\n {{ day.date.getDate() }}\n </button>\n </div>\n </div>\n\n <!-- Divider -->\n <div class=\"mld-datetime-picker__divider\" />\n\n <!-- Time section -->\n <div class=\"mld-datetime-picker__time-section\">\n <div class=\"mld-datetime-picker__time-label\">Time</div>\n <div class=\"mld-datetime-picker__time-grid\">\n <button\n v-for=\"time in timeSlots\"\n :key=\"time\"\n type=\"button\"\n :disabled=\"isTimeDisabled(time)\"\n :class=\"[\n 'mld-datetime-picker__time-chip',\n selectedTimeStr === time ? 'mld-datetime-picker__time-chip--active' : '',\n isTimeDisabled(time) ? 'mld-datetime-picker__time-chip--disabled' : '',\n ]\"\n @click=\"selectTime(time)\"\n >\n {{ timeFormat === '12h' ? formatTime(parseTime(time).hour, parseTime(time).minute, '12h') : time }}\n </button>\n </div>\n </div>\n\n <!-- Footer -->\n <div class=\"mld-datetime-picker__footer\">\n <div>\n <button type=\"button\" class=\"mld-datetime-picker__footer-btn\" @click=\"goToToday\">Today</button>\n <button type=\"button\" class=\"mld-datetime-picker__footer-btn\" @click=\"goToNow\">Now</button>\n </div>\n <button\n v-if=\"clearable && modelValue\"\n type=\"button\"\n class=\"mld-datetime-picker__footer-btn mld-datetime-picker__footer-btn--muted\"\n @click=\"clear\"\n >\n Clear\n </button>\n </div>\n </div>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/datetime-picker.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_normalizeClass","_createVNode","_Transition","_openBlock","_toDisplayString","_Fragment","_renderList","_unref"],"mappings":"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"}
|
|
@@ -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'\n\nexport interface MoleculeData {\n smiles: string\n molfile: string\n}\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// 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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"}
|
|
@@ -11,10 +11,10 @@ const _hoisted_5 = {
|
|
|
11
11
|
class: "mld-rack-editor__toolbar"
|
|
12
12
|
};
|
|
13
13
|
const _hoisted_6 = { class: "mld-rack-editor__toolbar-group" };
|
|
14
|
-
const _hoisted_7 = {
|
|
14
|
+
const _hoisted_7 = { class: "mld-rack-editor__toolbar-section" };
|
|
15
15
|
const _hoisted_8 = { class: "mld-rack-editor__format-btns" };
|
|
16
16
|
const _hoisted_9 = ["onClick"];
|
|
17
|
-
const _hoisted_10 = {
|
|
17
|
+
const _hoisted_10 = { class: "mld-rack-editor__toolbar-section" };
|
|
18
18
|
const _hoisted_11 = { class: "mld-rack-editor__slot-btns" };
|
|
19
19
|
const _hoisted_12 = ["onClick"];
|
|
20
20
|
const _hoisted_13 = {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RackEditor.vue.js","sources":["../../src/components/RackEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well } from '../types'\nimport { useRackEditor } from '../composables/useRackEditor'\nimport WellPlate from './WellPlate.vue'\n\ninterface Props {\n modelValue?: Rack[]\n activeRackId?: string\n maxRacks?: number\n minRacks?: number\n allowReorder?: boolean\n editable?: boolean\n readonly?: boolean\n wellPlateSize?: WellPlateSize\n showLegend?: boolean\n showBadges?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n activeRackId: undefined,\n maxRacks: 10,\n minRacks: 1,\n allowReorder: true,\n editable: true,\n readonly: false,\n wellPlateSize: 'md',\n showLegend: true,\n showBadges: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [racks: Rack[]]\n 'update:activeRackId': [rackId: string]\n 'rack-add': [rack: Rack]\n 'rack-remove': [rackId: string]\n 'rack-reorder': [rackIds: string[]]\n 'well-edit': [rackId: string, wellId: string, data: WellEditData]\n}>()\n\nconst editor = useRackEditor(props.modelValue, {\n maxRacks: props.maxRacks,\n minRacks: props.minRacks,\n})\n\n// Sync external modelValue -> internal racks\nwatch(\n () => props.modelValue,\n (newRacks) => {\n if (newRacks && JSON.stringify(newRacks) !== JSON.stringify(editor.racks.value)) {\n editor.racks.value = newRacks.map(r => ({ ...r }))\n }\n },\n)\n\n// Sync external activeRackId -> internal\nwatch(\n () => props.activeRackId,\n (id) => {\n if (id && id !== editor.activeRackId.value) {\n editor.setActiveRack(id)\n }\n },\n)\n\n// Sync internal racks -> external modelValue\nwatch(\n editor.racks,\n (newRacks) => {\n emit('update:modelValue', newRacks)\n },\n { deep: true },\n)\n\n// Sync internal activeRackId -> external\nwatch(\n editor.activeRackId,\n (id) => {\n emit('update:activeRackId', id)\n },\n)\n\nconst SLOT_COLORS: Record<SlotPosition, string> = {\n R: '#ef4444',\n G: '#22c55e',\n B: '#3b82f6',\n Y: '#eab308',\n}\n\nconst SLOT_OPTIONS: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\nconst FORMAT_OPTIONS: { value: WellPlateFormat; label: string }[] = [\n { value: 54, label: '54' },\n { value: 96, label: '96' },\n]\n\n// Drag state for rack reordering\nconst draggingRackId = ref<string | null>(null)\nconst dragOverRackId = ref<string | null>(null)\n\nfunction handleAddRack() {\n const rack = editor.addRack()\n emit('rack-add', rack)\n}\n\nfunction handleRemoveRack(rackId: string) {\n editor.removeRack(rackId)\n emit('rack-remove', rackId)\n}\n\nfunction handleTabClick(rackId: string) {\n editor.setActiveRack(rackId)\n}\n\n// Rack drag handlers\nfunction handleRackDragStart(event: DragEvent, rackId: string) {\n if (!props.allowReorder) return\n draggingRackId.value = rackId\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', rackId)\n }\n}\n\nfunction handleRackDragOver(event: DragEvent, rackId: string) {\n event.preventDefault()\n if (draggingRackId.value && draggingRackId.value !== rackId) {\n dragOverRackId.value = rackId\n }\n}\n\nfunction handleRackDragLeave() {\n dragOverRackId.value = null\n}\n\nfunction handleRackDrop(event: DragEvent, targetRackId: string) {\n event.preventDefault()\n if (!draggingRackId.value || draggingRackId.value === targetRackId) {\n clearDragState()\n return\n }\n\n const fromIndex = editor.racks.value.findIndex(r => r.id === draggingRackId.value)\n const toIndex = editor.racks.value.findIndex(r => r.id === targetRackId)\n\n if (fromIndex !== -1 && toIndex !== -1) {\n editor.reorderRacks(fromIndex, toIndex)\n emit('rack-reorder', editor.racks.value.map(r => r.id))\n }\n\n clearDragState()\n}\n\nfunction handleRackDragEnd() {\n clearDragState()\n}\n\nfunction clearDragState() {\n draggingRackId.value = null\n dragOverRackId.value = null\n}\n\n// Toolbar actions\nfunction handleFormatChange(format: WellPlateFormat) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { format })\n}\n\nfunction handleSlotChange(slot: SlotPosition) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { slot })\n}\n\nfunction handleClearAll() {\n if (!editor.activeRack.value) return\n editor.clearAllWells(editor.activeRack.value.id)\n}\n\nfunction handleFillSeries() {\n if (!editor.activeRack.value) return\n editor.fillSeries(editor.activeRack.value.id)\n}\n\n// Well edit from WellPlate\nfunction handleWellEdit(wellId: string, data: WellEditData) {\n if (!editor.activeRack.value) return\n const rackId = editor.activeRack.value.id\n\n const wellData: Partial<Well> = {\n id: wellId,\n state: data.label ? 'filled' : 'empty',\n sampleType: data.sampleType || undefined,\n metadata: {\n label: data.label,\n injectionVolume: data.injectionVolume,\n injectionCount: data.injectionCount,\n customMethod: data.customMethod || null,\n },\n }\n\n if (data.label) {\n editor.setWellData(rackId, wellId, wellData)\n } else {\n editor.clearWell(rackId, wellId)\n }\n\n emit('well-edit', rackId, wellId, data)\n}\n\nfunction handleWellClear(wellId: string) {\n if (!editor.activeRack.value) return\n editor.clearWell(editor.activeRack.value.id, wellId)\n}\n\n// Computed well count per rack\nfunction getWellCount(rack: Rack): number {\n return Object.keys(rack.wells).length\n}\n\nconst activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})\n</script>\n\n<template>\n <div :class=\"['mld-rack-editor', { 'mld-rack-editor--readonly': readonly }]\">\n <!-- Rack tabs -->\n <div class=\"mld-rack-editor__tabs\">\n <div class=\"mld-rack-editor__tabs-inner\">\n <button\n v-for=\"rack in editor.racks.value\"\n :key=\"rack.id\"\n :draggable=\"allowReorder && !readonly\"\n :class=\"[\n 'mld-rack-editor__tab',\n {\n 'mld-rack-editor__tab--active': editor.activeRackId.value === rack.id,\n 'mld-rack-editor__tab--dragging': draggingRackId === rack.id,\n 'mld-rack-editor__tab--drag-over': dragOverRackId === rack.id,\n },\n ]\"\n @click=\"handleTabClick(rack.id)\"\n @dragstart=\"handleRackDragStart($event, rack.id)\"\n @dragover=\"handleRackDragOver($event, rack.id)\"\n @dragleave=\"handleRackDragLeave\"\n @drop=\"handleRackDrop($event, rack.id)\"\n @dragend=\"handleRackDragEnd\"\n >\n <span\n class=\"mld-rack-editor__slot-dot\"\n :style=\"{ backgroundColor: SLOT_COLORS[rack.slot] }\"\n />\n {{ rack.name }}\n <span\n v-if=\"getWellCount(rack) > 0\"\n :class=\"[\n 'mld-rack-editor__count',\n editor.activeRackId.value === rack.id ? 'mld-rack-editor__count--active' : 'mld-rack-editor__count--inactive',\n ]\"\n >\n {{ getWellCount(rack) }}\n </span>\n <!-- Remove button -->\n <button\n v-if=\"editor.racks.value.length > minRacks && !readonly\"\n class=\"mld-rack-editor__tab-remove\"\n @click.stop=\"handleRemoveRack(rack.id)\"\n >\n ×\n </button>\n </button>\n </div>\n\n <!-- Add rack button -->\n <button\n v-if=\"editor.racks.value.length < maxRacks && !readonly\"\n class=\"mld-rack-editor__add-btn\"\n @click=\"handleAddRack\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n Add Rack\n </button>\n </div>\n\n <!-- Toolbar for active rack -->\n <div v-if=\"editor.activeRack.value && !readonly\" class=\"mld-rack-editor__toolbar\">\n <div class=\"mld-rack-editor__toolbar-group\">\n <!-- Format selector -->\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <span class=\"mld-rack-editor__toolbar-label\">Plate</span>\n <div class=\"mld-rack-editor__format-btns\">\n <button\n v-for=\"opt in FORMAT_OPTIONS\"\n :key=\"opt.value\"\n :class=\"[\n 'mld-rack-editor__format-btn',\n editor.activeRack.value.format === opt.value ? 'mld-rack-editor__format-btn--active' : 'mld-rack-editor__format-btn--inactive',\n ]\"\n @click=\"handleFormatChange(opt.value)\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mld-rack-editor__toolbar-divider\" />\n\n <!-- Slot selector -->\n <div style=\"display: flex; align-items: center; gap: 0.5rem;\">\n <span class=\"mld-rack-editor__toolbar-label\">Slot</span>\n <div class=\"mld-rack-editor__slot-btns\">\n <button\n v-for=\"s in SLOT_OPTIONS\"\n :key=\"s\"\n class=\"mld-rack-editor__slot-btn\"\n :style=\"{\n backgroundColor: editor.activeRack.value.slot === s ? SLOT_COLORS[s] : 'var(--bg-secondary)',\n color: editor.activeRack.value.slot === s ? 'white' : SLOT_COLORS[s],\n borderColor: editor.activeRack.value.slot === s ? 'transparent' : `${SLOT_COLORS[s]}40`,\n boxShadow: editor.activeRack.value.slot === s ? '0 2px 4px rgba(0,0,0,0.15)' : 'none',\n }\"\n @click=\"handleSlotChange(s)\"\n >\n {{ s }}\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mld-rack-editor__toolbar-spacer\" />\n\n <!-- Actions -->\n <button class=\"mld-rack-editor__action-btn\" @click=\"handleClearAll\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10 11v6\" /><path d=\"M14 11v6\" /><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\" /><path d=\"M3 6h18\" /><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n </svg>\n Clear\n </button>\n <button class=\"mld-rack-editor__action-btn\" @click=\"handleFillSeries\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" /><path d=\"M3 9h18\" /><path d=\"M3 15h18\" /><path d=\"M9 3v18\" /><path d=\"M15 3v18\" />\n </svg>\n Fill Series\n </button>\n </div>\n\n <!-- Plate view -->\n <div v-if=\"editor.activeRack.value\" class=\"mld-rack-editor__plate\">\n <WellPlate\n :format=\"editor.activeRack.value.format\"\n :wells=\"activeRackWells\"\n :editable=\"editable && !readonly\"\n :show-well-labels=\"true\"\n :show-badges=\"showBadges\"\n :show-legend=\"showLegend\"\n :default-injection-volume=\"editor.activeRack.value.injectionVolume\"\n :readonly=\"readonly\"\n :size=\"wellPlateSize\"\n :show-sample-type-indicator=\"true\"\n @well-edit=\"handleWellEdit\"\n @well-clear=\"handleWellClear\"\n />\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/rack-editor.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_openBlock","_Fragment","_unref","_normalizeClass","_normalizeStyle","_createTextVNode","_toDisplayString","_withModifiers","_renderList","_createVNode","WellPlate"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RackEditor.vue.js","sources":["../../src/components/RackEditor.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, computed, watch } from 'vue'\nimport type { Rack, SlotPosition, WellPlateFormat, WellPlateSize, WellEditData, Well } from '../types'\nimport { useRackEditor } from '../composables/useRackEditor'\nimport WellPlate from './WellPlate.vue'\n\ninterface Props {\n modelValue?: Rack[]\n activeRackId?: string\n maxRacks?: number\n minRacks?: number\n allowReorder?: boolean\n editable?: boolean\n readonly?: boolean\n wellPlateSize?: WellPlateSize\n showLegend?: boolean\n showBadges?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: undefined,\n activeRackId: undefined,\n maxRacks: 10,\n minRacks: 1,\n allowReorder: true,\n editable: true,\n readonly: false,\n wellPlateSize: 'md',\n showLegend: true,\n showBadges: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [racks: Rack[]]\n 'update:activeRackId': [rackId: string]\n 'rack-add': [rack: Rack]\n 'rack-remove': [rackId: string]\n 'rack-reorder': [rackIds: string[]]\n 'well-edit': [rackId: string, wellId: string, data: WellEditData]\n}>()\n\nconst editor = useRackEditor(props.modelValue, {\n maxRacks: props.maxRacks,\n minRacks: props.minRacks,\n})\n\n// Sync external modelValue -> internal racks\nwatch(\n () => props.modelValue,\n (newRacks) => {\n if (newRacks && JSON.stringify(newRacks) !== JSON.stringify(editor.racks.value)) {\n editor.racks.value = newRacks.map(r => ({ ...r }))\n }\n },\n)\n\n// Sync external activeRackId -> internal\nwatch(\n () => props.activeRackId,\n (id) => {\n if (id && id !== editor.activeRackId.value) {\n editor.setActiveRack(id)\n }\n },\n)\n\n// Sync internal racks -> external modelValue\nwatch(\n editor.racks,\n (newRacks) => {\n emit('update:modelValue', newRacks)\n },\n { deep: true },\n)\n\n// Sync internal activeRackId -> external\nwatch(\n editor.activeRackId,\n (id) => {\n emit('update:activeRackId', id)\n },\n)\n\nconst SLOT_COLORS: Record<SlotPosition, string> = {\n R: '#ef4444',\n G: '#22c55e',\n B: '#3b82f6',\n Y: '#eab308',\n}\n\nconst SLOT_OPTIONS: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\nconst FORMAT_OPTIONS: { value: WellPlateFormat; label: string }[] = [\n { value: 54, label: '54' },\n { value: 96, label: '96' },\n]\n\n// Drag state for rack reordering\nconst draggingRackId = ref<string | null>(null)\nconst dragOverRackId = ref<string | null>(null)\n\nfunction handleAddRack() {\n const rack = editor.addRack()\n emit('rack-add', rack)\n}\n\nfunction handleRemoveRack(rackId: string) {\n editor.removeRack(rackId)\n emit('rack-remove', rackId)\n}\n\nfunction handleTabClick(rackId: string) {\n editor.setActiveRack(rackId)\n}\n\n// Rack drag handlers\nfunction handleRackDragStart(event: DragEvent, rackId: string) {\n if (!props.allowReorder) return\n draggingRackId.value = rackId\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', rackId)\n }\n}\n\nfunction handleRackDragOver(event: DragEvent, rackId: string) {\n event.preventDefault()\n if (draggingRackId.value && draggingRackId.value !== rackId) {\n dragOverRackId.value = rackId\n }\n}\n\nfunction handleRackDragLeave() {\n dragOverRackId.value = null\n}\n\nfunction handleRackDrop(event: DragEvent, targetRackId: string) {\n event.preventDefault()\n if (!draggingRackId.value || draggingRackId.value === targetRackId) {\n clearDragState()\n return\n }\n\n const fromIndex = editor.racks.value.findIndex(r => r.id === draggingRackId.value)\n const toIndex = editor.racks.value.findIndex(r => r.id === targetRackId)\n\n if (fromIndex !== -1 && toIndex !== -1) {\n editor.reorderRacks(fromIndex, toIndex)\n emit('rack-reorder', editor.racks.value.map(r => r.id))\n }\n\n clearDragState()\n}\n\nfunction handleRackDragEnd() {\n clearDragState()\n}\n\nfunction clearDragState() {\n draggingRackId.value = null\n dragOverRackId.value = null\n}\n\n// Toolbar actions\nfunction handleFormatChange(format: WellPlateFormat) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { format })\n}\n\nfunction handleSlotChange(slot: SlotPosition) {\n if (!editor.activeRack.value) return\n editor.updateRack(editor.activeRack.value.id, { slot })\n}\n\nfunction handleClearAll() {\n if (!editor.activeRack.value) return\n editor.clearAllWells(editor.activeRack.value.id)\n}\n\nfunction handleFillSeries() {\n if (!editor.activeRack.value) return\n editor.fillSeries(editor.activeRack.value.id)\n}\n\n// Well edit from WellPlate\nfunction handleWellEdit(wellId: string, data: WellEditData) {\n if (!editor.activeRack.value) return\n const rackId = editor.activeRack.value.id\n\n const wellData: Partial<Well> = {\n id: wellId,\n state: data.label ? 'filled' : 'empty',\n sampleType: data.sampleType || undefined,\n metadata: {\n label: data.label,\n injectionVolume: data.injectionVolume,\n injectionCount: data.injectionCount,\n customMethod: data.customMethod || null,\n },\n }\n\n if (data.label) {\n editor.setWellData(rackId, wellId, wellData)\n } else {\n editor.clearWell(rackId, wellId)\n }\n\n emit('well-edit', rackId, wellId, data)\n}\n\nfunction handleWellClear(wellId: string) {\n if (!editor.activeRack.value) return\n editor.clearWell(editor.activeRack.value.id, wellId)\n}\n\n// Computed well count per rack\nfunction getWellCount(rack: Rack): number {\n return Object.keys(rack.wells).length\n}\n\nconst activeRackWells = computed(() => editor.activeRack.value?.wells ?? {})\n</script>\n\n<template>\n <div :class=\"['mld-rack-editor', { 'mld-rack-editor--readonly': readonly }]\">\n <!-- Rack tabs -->\n <div class=\"mld-rack-editor__tabs\">\n <div class=\"mld-rack-editor__tabs-inner\">\n <button\n v-for=\"rack in editor.racks.value\"\n :key=\"rack.id\"\n :draggable=\"allowReorder && !readonly\"\n :class=\"[\n 'mld-rack-editor__tab',\n {\n 'mld-rack-editor__tab--active': editor.activeRackId.value === rack.id,\n 'mld-rack-editor__tab--dragging': draggingRackId === rack.id,\n 'mld-rack-editor__tab--drag-over': dragOverRackId === rack.id,\n },\n ]\"\n @click=\"handleTabClick(rack.id)\"\n @dragstart=\"handleRackDragStart($event, rack.id)\"\n @dragover=\"handleRackDragOver($event, rack.id)\"\n @dragleave=\"handleRackDragLeave\"\n @drop=\"handleRackDrop($event, rack.id)\"\n @dragend=\"handleRackDragEnd\"\n >\n <span\n class=\"mld-rack-editor__slot-dot\"\n :style=\"{ backgroundColor: SLOT_COLORS[rack.slot] }\"\n />\n {{ rack.name }}\n <span\n v-if=\"getWellCount(rack) > 0\"\n :class=\"[\n 'mld-rack-editor__count',\n editor.activeRackId.value === rack.id ? 'mld-rack-editor__count--active' : 'mld-rack-editor__count--inactive',\n ]\"\n >\n {{ getWellCount(rack) }}\n </span>\n <!-- Remove button -->\n <button\n v-if=\"editor.racks.value.length > minRacks && !readonly\"\n class=\"mld-rack-editor__tab-remove\"\n @click.stop=\"handleRemoveRack(rack.id)\"\n >\n ×\n </button>\n </button>\n </div>\n\n <!-- Add rack button -->\n <button\n v-if=\"editor.racks.value.length < maxRacks && !readonly\"\n class=\"mld-rack-editor__add-btn\"\n @click=\"handleAddRack\"\n >\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M5 12h14\" /><path d=\"M12 5v14\" />\n </svg>\n Add Rack\n </button>\n </div>\n\n <!-- Toolbar for active rack -->\n <div v-if=\"editor.activeRack.value && !readonly\" class=\"mld-rack-editor__toolbar\">\n <div class=\"mld-rack-editor__toolbar-group\">\n <!-- Format selector -->\n <div class=\"mld-rack-editor__toolbar-section\">\n <span class=\"mld-rack-editor__toolbar-label\">Plate</span>\n <div class=\"mld-rack-editor__format-btns\">\n <button\n v-for=\"opt in FORMAT_OPTIONS\"\n :key=\"opt.value\"\n :class=\"[\n 'mld-rack-editor__format-btn',\n editor.activeRack.value.format === opt.value ? 'mld-rack-editor__format-btn--active' : 'mld-rack-editor__format-btn--inactive',\n ]\"\n @click=\"handleFormatChange(opt.value)\"\n >\n {{ opt.label }}\n </button>\n </div>\n </div>\n\n <div class=\"mld-rack-editor__toolbar-divider\" />\n\n <!-- Slot selector -->\n <div class=\"mld-rack-editor__toolbar-section\">\n <span class=\"mld-rack-editor__toolbar-label\">Slot</span>\n <div class=\"mld-rack-editor__slot-btns\">\n <button\n v-for=\"s in SLOT_OPTIONS\"\n :key=\"s\"\n class=\"mld-rack-editor__slot-btn\"\n :style=\"{\n backgroundColor: editor.activeRack.value.slot === s ? SLOT_COLORS[s] : 'var(--bg-secondary)',\n color: editor.activeRack.value.slot === s ? 'white' : SLOT_COLORS[s],\n borderColor: editor.activeRack.value.slot === s ? 'transparent' : `${SLOT_COLORS[s]}40`,\n boxShadow: editor.activeRack.value.slot === s ? '0 2px 4px rgba(0,0,0,0.15)' : 'none',\n }\"\n @click=\"handleSlotChange(s)\"\n >\n {{ s }}\n </button>\n </div>\n </div>\n </div>\n\n <div class=\"mld-rack-editor__toolbar-spacer\" />\n\n <!-- Actions -->\n <button class=\"mld-rack-editor__action-btn\" @click=\"handleClearAll\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10 11v6\" /><path d=\"M14 11v6\" /><path d=\"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6\" /><path d=\"M3 6h18\" /><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" />\n </svg>\n Clear\n </button>\n <button class=\"mld-rack-editor__action-btn\" @click=\"handleFillSeries\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" /><path d=\"M3 9h18\" /><path d=\"M3 15h18\" /><path d=\"M9 3v18\" /><path d=\"M15 3v18\" />\n </svg>\n Fill Series\n </button>\n </div>\n\n <!-- Plate view -->\n <div v-if=\"editor.activeRack.value\" class=\"mld-rack-editor__plate\">\n <WellPlate\n :format=\"editor.activeRack.value.format\"\n :wells=\"activeRackWells\"\n :editable=\"editable && !readonly\"\n :show-well-labels=\"true\"\n :show-badges=\"showBadges\"\n :show-legend=\"showLegend\"\n :default-injection-volume=\"editor.activeRack.value.injectionVolume\"\n :readonly=\"readonly\"\n :size=\"wellPlateSize\"\n :show-sample-type-indicator=\"true\"\n @well-edit=\"handleWellEdit\"\n @well-clear=\"handleWellClear\"\n />\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/rack-editor.css';\n</style>\n"],"names":["_createElementBlock","_createElementVNode","_openBlock","_Fragment","_unref","_normalizeClass","_normalizeStyle","_createTextVNode","_toDisplayString","_withModifiers","_renderList","_createVNode","WellPlate"],"mappings":"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"}
|
|
@@ -1,18 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export type ReagentColumn = 'name' | 'catalog' | 'lot' | 'expiry' | 'storage' | 'location' | 'stock' | 'supplier';
|
|
3
|
-
export interface Reagent {
|
|
4
|
-
id: string;
|
|
5
|
-
name: string;
|
|
6
|
-
catalogNumber?: string;
|
|
7
|
-
lotNumber?: string;
|
|
8
|
-
expiryDate?: Date | string;
|
|
9
|
-
storageCondition?: StorageCondition;
|
|
10
|
-
location?: string;
|
|
11
|
-
stockLevel?: number;
|
|
12
|
-
stockUnit?: string;
|
|
13
|
-
supplier?: string;
|
|
14
|
-
url?: string;
|
|
15
|
-
}
|
|
1
|
+
import { ReagentColumn, Reagent } from '../types';
|
|
16
2
|
interface Props {
|
|
17
3
|
modelValue?: Reagent[];
|
|
18
4
|
readonly?: boolean;
|