@morscherlab/mint-sdk 1.0.0-alpha.8 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +15 -15
  2. package/dist/{auth-BYmxZdJl.js → auth-DsI0rQ7_.js} +6 -6
  3. package/dist/auth-DsI0rQ7_.js.map +1 -0
  4. package/dist/components/index.js +2 -2
  5. package/dist/{components-CKf-UpGi.js → components-CzbQQPCb.js} +1429 -1429
  6. package/dist/components-CzbQQPCb.js.map +1 -0
  7. package/dist/composables/index.js +2 -2
  8. package/dist/composables/usePlatformContext.d.ts +3 -3
  9. package/dist/{composables-D0QfFzq1.js → composables-BXklV5ii.js} +3 -3
  10. package/dist/{composables-D0QfFzq1.js.map → composables-BXklV5ii.js.map} +1 -1
  11. package/dist/index.js +4 -4
  12. package/dist/install.d.ts +3 -3
  13. package/dist/install.js +5 -5
  14. package/dist/install.js.map +1 -1
  15. package/dist/stores/auth.d.ts +1 -1
  16. package/dist/stores/index.js +1 -1
  17. package/dist/stores/settings.d.ts +1 -1
  18. package/dist/styles.css +5388 -5388
  19. package/dist/types/platform.d.ts +1 -1
  20. package/dist/{useScheduleDrag-DAJueTbK.js → useScheduleDrag-CxBeqYcu.js} +331 -331
  21. package/dist/useScheduleDrag-CxBeqYcu.js.map +1 -0
  22. package/package.json +2 -2
  23. package/src/__tests__/components/AppLayout.test.ts +23 -23
  24. package/src/__tests__/components/AppSidebar.test.ts +29 -29
  25. package/src/__tests__/components/AppTopBar.test.ts +45 -45
  26. package/src/__tests__/components/BaseInput.test.ts +2 -2
  27. package/src/__tests__/components/BasePill.test.ts +37 -37
  28. package/src/__tests__/components/Calendar.test.ts +52 -52
  29. package/src/__tests__/components/CollapsibleCard.test.ts +81 -81
  30. package/src/__tests__/components/DataFrame.test.ts +80 -80
  31. package/src/__tests__/components/DropdownButton.test.ts +80 -80
  32. package/src/__tests__/composables/usePlatformContext.test.ts +1 -1
  33. package/src/components/AlertBox.story.vue +1 -1
  34. package/src/components/AlertBox.vue +14 -14
  35. package/src/components/AppAvatarMenu.vue +26 -26
  36. package/src/components/AppContainer.vue +3 -3
  37. package/src/components/AppLayout.vue +7 -7
  38. package/src/components/AppPageSelector.vue +30 -30
  39. package/src/components/AppPillNav.vue +10 -10
  40. package/src/components/AppPluginSwitcher.vue +31 -31
  41. package/src/components/AppSidebar.vue +8 -8
  42. package/src/components/AppTopBar.story.vue +7 -7
  43. package/src/components/AppTopBar.vue +102 -102
  44. package/src/components/AuditTrail.vue +19 -19
  45. package/src/components/AutoGroupModal.vue +76 -76
  46. package/src/components/Avatar.vue +6 -6
  47. package/src/components/BaseButton.vue +6 -6
  48. package/src/components/BaseCheckbox.vue +9 -9
  49. package/src/components/BaseInput.vue +4 -4
  50. package/src/components/BaseModal.story.vue +1 -1
  51. package/src/components/BaseModal.vue +14 -14
  52. package/src/components/BasePill.vue +9 -9
  53. package/src/components/BaseRadioGroup.vue +21 -21
  54. package/src/components/BaseSelect.vue +6 -6
  55. package/src/components/BaseSlider.vue +8 -8
  56. package/src/components/BaseTabs.vue +7 -7
  57. package/src/components/BaseTextarea.vue +5 -5
  58. package/src/components/BaseToggle.vue +10 -10
  59. package/src/components/BatchProgressList.vue +25 -25
  60. package/src/components/Breadcrumb.vue +8 -8
  61. package/src/components/Calendar.vue +19 -19
  62. package/src/components/ChartContainer.vue +9 -9
  63. package/src/components/ChemicalFormula.vue +7 -7
  64. package/src/components/CollapsibleCard.vue +20 -20
  65. package/src/components/ColorSlider.vue +6 -6
  66. package/src/components/ConcentrationInput.vue +12 -12
  67. package/src/components/ConfirmDialog.story.vue +1 -1
  68. package/src/components/ConfirmDialog.vue +7 -7
  69. package/src/components/DataFrame.vue +40 -40
  70. package/src/components/DatePicker.vue +29 -29
  71. package/src/components/DateTimePicker.vue +41 -41
  72. package/src/components/Divider.vue +9 -9
  73. package/src/components/DoseCalculator.vue +66 -66
  74. package/src/components/DropdownButton.vue +19 -19
  75. package/src/components/EmptyState.vue +9 -9
  76. package/src/components/ExperimentCodeBadge.vue +3 -3
  77. package/src/components/ExperimentDataViewer.vue +25 -25
  78. package/src/components/ExperimentPopover.vue +35 -35
  79. package/src/components/ExperimentSelectorModal.vue +40 -40
  80. package/src/components/ExperimentTimeline.vue +48 -48
  81. package/src/components/FileUploader.vue +31 -31
  82. package/src/components/FitPanel.vue +9 -9
  83. package/src/components/FormActions.vue +1 -1
  84. package/src/components/FormBuilder.vue +2 -2
  85. package/src/components/FormField.vue +7 -7
  86. package/src/components/FormSection.vue +7 -7
  87. package/src/components/FormulaInput.vue +10 -10
  88. package/src/components/GroupAssigner.vue +40 -40
  89. package/src/components/GroupingModal.vue +45 -45
  90. package/src/components/IconButton.vue +6 -6
  91. package/src/components/LoadingSpinner.vue +5 -5
  92. package/src/components/MoleculeInput.vue +21 -21
  93. package/src/components/MultiSelect.vue +13 -13
  94. package/src/components/NumberInput.vue +13 -13
  95. package/src/components/PlateMapEditor.vue +63 -63
  96. package/src/components/ProgressBar.vue +18 -18
  97. package/src/components/ProtocolStepEditor.vue +57 -57
  98. package/src/components/RackEditor.vue +28 -28
  99. package/src/components/ReagentEditor.vue +61 -61
  100. package/src/components/ReagentList.vue +49 -49
  101. package/src/components/ResourceCard.vue +28 -28
  102. package/src/components/SampleHierarchyTree.vue +13 -13
  103. package/src/components/SampleLegend.vue +12 -12
  104. package/src/components/SampleSelector.vue +104 -104
  105. package/src/components/ScheduleCalendar.vue +42 -42
  106. package/src/components/ScientificNumber.vue +11 -11
  107. package/src/components/SegmentedControl.vue +12 -12
  108. package/src/components/SequenceInput.vue +32 -32
  109. package/src/components/SettingsButton.vue +5 -5
  110. package/src/components/SettingsModal.vue +17 -17
  111. package/src/components/StatusIndicator.vue +5 -5
  112. package/src/components/StepWizard.vue +16 -16
  113. package/src/components/TagsInput.vue +20 -20
  114. package/src/components/ThemeToggle.vue +3 -3
  115. package/src/components/TimePicker.vue +21 -21
  116. package/src/components/TimeRangeInput.vue +5 -5
  117. package/src/components/ToastNotification.vue +8 -8
  118. package/src/components/Tooltip.vue +7 -7
  119. package/src/components/UnitInput.vue +12 -12
  120. package/src/components/WellEditPopup.vue +28 -28
  121. package/src/components/WellPlate.vue +37 -37
  122. package/src/composables/useAppExperiment.ts +1 -1
  123. package/src/composables/usePlatformContext.ts +16 -16
  124. package/src/composables/useProtocolTemplates.ts +1 -1
  125. package/src/install.ts +3 -3
  126. package/src/stores/auth.ts +3 -3
  127. package/src/stores/settings.ts +2 -2
  128. package/src/styles/components/alert-box.css +30 -30
  129. package/src/styles/components/app-avatar-menu.css +23 -23
  130. package/src/styles/components/app-container.css +6 -6
  131. package/src/styles/components/app-layout.css +15 -15
  132. package/src/styles/components/app-page-selector.css +26 -26
  133. package/src/styles/components/app-pill-nav.css +7 -7
  134. package/src/styles/components/app-plugin-switcher.css +27 -27
  135. package/src/styles/components/app-sidebar.css +24 -24
  136. package/src/styles/components/app-top-bar.css +65 -65
  137. package/src/styles/components/audit-trail.css +29 -29
  138. package/src/styles/components/auto-group-modal.css +91 -91
  139. package/src/styles/components/avatar.css +15 -15
  140. package/src/styles/components/batch-progress-list.css +40 -40
  141. package/src/styles/components/breadcrumb.css +8 -8
  142. package/src/styles/components/button.css +31 -31
  143. package/src/styles/components/calendar.css +27 -27
  144. package/src/styles/components/chart-container.css +9 -9
  145. package/src/styles/components/checkbox.css +20 -20
  146. package/src/styles/components/chemical-formula.css +8 -8
  147. package/src/styles/components/collapsible-card.css +35 -35
  148. package/src/styles/components/color-slider.css +8 -8
  149. package/src/styles/components/concentration-input.css +27 -27
  150. package/src/styles/components/confirm-dialog.css +32 -32
  151. package/src/styles/components/dataframe.css +66 -66
  152. package/src/styles/components/date-picker.css +40 -40
  153. package/src/styles/components/datetime-picker.css +37 -37
  154. package/src/styles/components/divider.css +13 -13
  155. package/src/styles/components/dose-calculator.css +43 -43
  156. package/src/styles/components/dropdown-button.css +46 -46
  157. package/src/styles/components/empty-state.css +44 -44
  158. package/src/styles/components/experiment-code-badge.css +8 -8
  159. package/src/styles/components/experiment-data-viewer.css +23 -23
  160. package/src/styles/components/experiment-popover.css +97 -97
  161. package/src/styles/components/experiment-selector-modal.css +39 -39
  162. package/src/styles/components/experiment-timeline.css +98 -98
  163. package/src/styles/components/file-uploader.css +44 -44
  164. package/src/styles/components/fit-panel.css +12 -12
  165. package/src/styles/components/form-builder.css +11 -11
  166. package/src/styles/components/form-field.css +7 -7
  167. package/src/styles/components/formula-input.css +17 -17
  168. package/src/styles/components/group-assigner.css +26 -26
  169. package/src/styles/components/grouping-modal.css +51 -51
  170. package/src/styles/components/icon-button.css +41 -41
  171. package/src/styles/components/input.css +13 -13
  172. package/src/styles/components/loading-spinner.css +12 -12
  173. package/src/styles/components/modal.css +69 -69
  174. package/src/styles/components/molecule-input.css +27 -27
  175. package/src/styles/components/multi-select.css +23 -23
  176. package/src/styles/components/number-input.css +32 -32
  177. package/src/styles/components/pill.css +37 -37
  178. package/src/styles/components/plate-map-editor.css +67 -67
  179. package/src/styles/components/progress-bar.css +41 -41
  180. package/src/styles/components/protocol-step-editor.css +63 -63
  181. package/src/styles/components/rack-editor.css +34 -34
  182. package/src/styles/components/radio-group.css +41 -41
  183. package/src/styles/components/reagent-editor.css +70 -70
  184. package/src/styles/components/reagent-list.css +65 -65
  185. package/src/styles/components/resource-card.css +52 -52
  186. package/src/styles/components/sample-hierarchy-tree.css +56 -56
  187. package/src/styles/components/sample-legend.css +37 -37
  188. package/src/styles/components/sample-selector.css +121 -121
  189. package/src/styles/components/schedule-calendar.css +67 -67
  190. package/src/styles/components/scientific-number.css +11 -11
  191. package/src/styles/components/segmented-control.css +33 -33
  192. package/src/styles/components/select.css +11 -11
  193. package/src/styles/components/sequence-input.css +29 -29
  194. package/src/styles/components/settings-button.css +16 -16
  195. package/src/styles/components/settings-modal.css +14 -14
  196. package/src/styles/components/skeleton.css +2 -2
  197. package/src/styles/components/slider.css +10 -10
  198. package/src/styles/components/status-indicator.css +12 -12
  199. package/src/styles/components/step-wizard.css +32 -32
  200. package/src/styles/components/tabs.css +16 -16
  201. package/src/styles/components/tags-input.css +46 -46
  202. package/src/styles/components/textarea.css +17 -17
  203. package/src/styles/components/theme-toggle.css +13 -13
  204. package/src/styles/components/time-picker.css +28 -28
  205. package/src/styles/components/time-range-input.css +8 -8
  206. package/src/styles/components/toast.css +18 -18
  207. package/src/styles/components/toggle.css +27 -27
  208. package/src/styles/components/tooltip.css +18 -18
  209. package/src/styles/components/unit-input.css +25 -25
  210. package/src/styles/components/well-edit-popup.css +32 -32
  211. package/src/styles/components/well-plate.css +49 -49
  212. package/src/styles/index.css +1 -1
  213. package/src/styles/variables.css +3 -3
  214. package/src/types/platform.ts +6 -6
  215. package/dist/auth-BYmxZdJl.js.map +0 -1
  216. package/dist/components-CKf-UpGi.js.map +0 -1
  217. package/dist/useScheduleDrag-DAJueTbK.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"useScheduleDrag-DAJueTbK.js","names":["$slots"],"sources":["../src/components/BaseInput.vue","../src/components/BaseInput.vue","../src/components/BaseTextarea.vue","../src/components/BaseTextarea.vue","../src/components/BaseSelect.vue","../src/components/BaseSelect.vue","../src/components/BaseCheckbox.vue","../src/components/BaseCheckbox.vue","../src/components/BaseToggle.vue","../src/components/BaseToggle.vue","../src/components/BaseRadioGroup.vue","../src/components/BaseRadioGroup.vue","../src/components/BaseSlider.vue","../src/components/BaseSlider.vue","../src/components/MultiSelect.vue","../src/components/MultiSelect.vue","../src/components/DatePicker.vue","../src/components/DatePicker.vue","../src/composables/useTimeUtils.ts","../src/components/TimePicker.vue","../src/components/TimePicker.vue","../src/components/TagsInput.vue","../src/components/TagsInput.vue","../src/components/NumberInput.vue","../src/components/NumberInput.vue","../src/components/FileUploader.vue","../src/components/FileUploader.vue","../src/composables/useToast.ts","../src/composables/useTheme.ts","../src/composables/useApi.ts","../src/composables/experiment-utils.ts","../src/composables/useExperimentSelector.ts","../src/composables/usePlatformContext.ts","../src/composables/useAppExperiment.ts","../src/composables/useRackEditor.ts","../src/composables/useWellPlateEditor.ts","../src/composables/useAutoGroup.ts","../src/components/MoleculeInput.vue","../src/components/MoleculeInput.vue","../src/composables/useConcentrationUnits.ts","../src/components/ConcentrationInput.vue","../src/components/ConcentrationInput.vue","../src/composables/useDoseCalculator.ts","../src/composables/useReagentSeries.ts","../src/composables/useProtocolTemplates.ts","../src/composables/useChemicalFormula.ts","../src/components/FormulaInput.vue","../src/components/FormulaInput.vue","../src/composables/useSequenceUtils.ts","../src/components/SequenceInput.vue","../src/components/SequenceInput.vue","../src/components/UnitInput.vue","../src/components/UnitInput.vue","../src/composables/useForm.ts","../src/components/DateTimePicker.vue","../src/components/DateTimePicker.vue","../src/composables/formBuilderRegistry.ts","../src/composables/useFormBuilder.ts","../src/composables/useExperimentData.ts","../src/composables/useScheduleDrag.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mld-input',\n `mld-input--${size}`,\n error ? 'mld-input--error' : '',\n disabled ? 'mld-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled `<input>` supporting text/number/email types, size, error, and readonly states. */\nimport type { InputType } from '../types'\n\ninterface Props {\n modelValue?: string | number\n type?: InputType\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n autocomplete?: string\n autofocus?: boolean\n min?: number\n max?: number\n step?: number\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n type: 'text',\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number | null]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n keydown: [event: KeyboardEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = props.type === 'number'\n ? (target.value === '' ? null : Number(target.value))\n : target.value\n emit('update:modelValue', value as string | number)\n}\n</script>\n\n<template>\n <input\n :type=\"type\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :readonly=\"readonly\"\n :autocomplete=\"autocomplete\"\n :autofocus=\"autofocus\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mld-input',\n `mld-input--${size}`,\n error ? 'mld-input--error' : '',\n disabled ? 'mld-input--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n @keydown=\"emit('keydown', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled `<textarea>` with configurable rows, resize mode, size, and error state. */\ninterface Props {\n modelValue?: string\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n rows?: number\n resize?: 'none' | 'vertical' | 'horizontal' | 'both'\n maxlength?: number\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n rows: 3,\n resize: 'vertical',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLTextAreaElement\n emit('update:modelValue', target.value)\n}\n</script>\n\n<template>\n <textarea\n :value=\"props.modelValue\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :readonly=\"props.readonly\"\n :rows=\"props.rows\"\n :maxlength=\"props.maxlength\"\n :aria-invalid=\"props.error || undefined\"\n :aria-describedby=\"props.ariaDescribedby || undefined\"\n :class=\"[\n 'mld-textarea',\n `mld-textarea--${props.size}`,\n `mld-textarea--resize-${props.resize}`,\n props.error ? 'mld-textarea--error' : '',\n props.disabled ? 'mld-textarea--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/textarea.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled `<textarea>` with configurable rows, resize mode, size, and error state. */\ninterface Props {\n modelValue?: string\n placeholder?: string\n disabled?: boolean\n readonly?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n rows?: number\n resize?: 'none' | 'vertical' | 'horizontal' | 'both'\n maxlength?: number\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n readonly: false,\n error: false,\n size: 'md',\n rows: 3,\n resize: 'vertical',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n focus: [event: FocusEvent]\n blur: [event: FocusEvent]\n}>()\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLTextAreaElement\n emit('update:modelValue', target.value)\n}\n</script>\n\n<template>\n <textarea\n :value=\"props.modelValue\"\n :placeholder=\"props.placeholder\"\n :disabled=\"props.disabled\"\n :readonly=\"props.readonly\"\n :rows=\"props.rows\"\n :maxlength=\"props.maxlength\"\n :aria-invalid=\"props.error || undefined\"\n :aria-describedby=\"props.ariaDescribedby || undefined\"\n :class=\"[\n 'mld-textarea',\n `mld-textarea--${props.size}`,\n `mld-textarea--resize-${props.resize}`,\n props.error ? 'mld-textarea--error' : '',\n props.disabled ? 'mld-textarea--disabled' : '',\n ]\"\n @input=\"handleInput\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n />\n</template>\n\n<style>\n@import '../styles/components/textarea.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport type { SelectOption } from '../types'\n\ninterface Props {\n modelValue?: string | number\n options: SelectOption<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const value = typeof props.modelValue === 'number'\n ? Number(target.value)\n : target.value\n emit('update:modelValue', value)\n}\n</script>\n\n<template>\n <div class=\"mld-select\">\n <select\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mld-select__control',\n `mld-select__control--${size}`,\n error ? 'mld-select__control--error' : '',\n disabled ? 'mld-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in options\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mld-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a native `<select>` element with size, error, and disabled states. */\nimport type { SelectOption } from '../types'\n\ninterface Props {\n modelValue?: string | number\n options: SelectOption<string | number>[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n ariaDescribedby?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const value = typeof props.modelValue === 'number'\n ? Number(target.value)\n : target.value\n emit('update:modelValue', value)\n}\n</script>\n\n<template>\n <div class=\"mld-select\">\n <select\n :value=\"modelValue\"\n :disabled=\"disabled\"\n :aria-invalid=\"error || undefined\"\n :aria-describedby=\"ariaDescribedby || undefined\"\n :class=\"[\n 'mld-select__control',\n `mld-select__control--${size}`,\n error ? 'mld-select__control--error' : '',\n disabled ? 'mld-select__control--disabled' : '',\n ]\"\n @change=\"handleChange\"\n >\n <option v-if=\"placeholder\" value=\"\" disabled>\n {{ placeholder }}\n </option>\n <option\n v-for=\"option in options\"\n :key=\"String(option.value)\"\n :value=\"option.value\"\n :disabled=\"option.disabled\"\n >\n {{ option.label }}\n </option>\n </select>\n <div class=\"mld-select__icon\">\n <svg fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled checkbox with an optional label, size variants, and disabled state. */\ninterface Props {\n modelValue?: boolean\n label?: string\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: false,\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.checked)\n}\n</script>\n\n<template>\n <label\n :class=\"[\n 'mld-checkbox',\n props.disabled ? 'mld-checkbox--disabled' : '',\n ]\"\n >\n <div class=\"mld-checkbox__input-wrapper\">\n <input\n type=\"checkbox\"\n :checked=\"props.modelValue\"\n :disabled=\"props.disabled\"\n :aria-label=\"props.label || 'Checkbox'\"\n class=\"mld-checkbox__native\"\n @change=\"handleChange\"\n />\n <div\n :class=\"[\n 'mld-checkbox__box',\n `mld-checkbox__box--${props.size}`,\n props.modelValue ? 'mld-checkbox__box--checked' : '',\n ]\"\n >\n <svg\n v-if=\"props.modelValue\"\n :class=\"['mld-checkbox__icon', `mld-checkbox__icon--${props.size}`]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n </div>\n <span v-if=\"props.label\" :class=\"['mld-checkbox__label', `mld-checkbox__label--${props.size}`]\">\n {{ props.label }}\n </span>\n </label>\n</template>\n\n<style>\n@import '../styles/components/checkbox.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a styled checkbox with an optional label, size variants, and disabled state. */\ninterface Props {\n modelValue?: boolean\n label?: string\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: false,\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nfunction handleChange(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.checked)\n}\n</script>\n\n<template>\n <label\n :class=\"[\n 'mld-checkbox',\n props.disabled ? 'mld-checkbox--disabled' : '',\n ]\"\n >\n <div class=\"mld-checkbox__input-wrapper\">\n <input\n type=\"checkbox\"\n :checked=\"props.modelValue\"\n :disabled=\"props.disabled\"\n :aria-label=\"props.label || 'Checkbox'\"\n class=\"mld-checkbox__native\"\n @change=\"handleChange\"\n />\n <div\n :class=\"[\n 'mld-checkbox__box',\n `mld-checkbox__box--${props.size}`,\n props.modelValue ? 'mld-checkbox__box--checked' : '',\n ]\"\n >\n <svg\n v-if=\"props.modelValue\"\n :class=\"['mld-checkbox__icon', `mld-checkbox__icon--${props.size}`]\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\" />\n </svg>\n </div>\n </div>\n <span v-if=\"props.label\" :class=\"['mld-checkbox__label', `mld-checkbox__label--${props.size}`]\">\n {{ props.label }}\n </span>\n </label>\n</template>\n\n<style>\n@import '../styles/components/checkbox.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders an on/off toggle switch with label, size, and keyboard interaction support. */\ninterface Props {\n modelValue?: boolean\n label?: string\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n reverse?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: false,\n disabled: false,\n size: 'md',\n reverse: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nfunction toggle() {\n if (!props.disabled) {\n emit('update:modelValue', !props.modelValue)\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n toggle()\n }\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-toggle',\n reverse ? 'mld-toggle--reverse' : '',\n disabled ? 'mld-toggle--disabled' : '',\n ]\"\n @click=\"toggle\"\n >\n <div\n role=\"switch\"\n :tabindex=\"disabled ? -1 : 0\"\n :aria-checked=\"modelValue\"\n :aria-label=\"label || 'Toggle'\"\n :class=\"[\n 'mld-toggle__track',\n `mld-toggle__track--${size}`,\n modelValue ? 'mld-toggle__track--on' : 'mld-toggle__track--off',\n ]\"\n @keydown=\"handleKeydown\"\n >\n <span\n :class=\"[\n 'mld-toggle__knob',\n `mld-toggle__knob--${size}`,\n modelValue ? `mld-toggle__knob--on-${size}` : 'mld-toggle__knob--off',\n ]\"\n />\n </div>\n <span\n v-if=\"label\"\n :class=\"['mld-toggle__label', `mld-toggle__label--${size}`]\"\n >\n {{ label }}\n </span>\n </div>\n</template>\n\n<style>\n@import '../styles/components/toggle.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders an on/off toggle switch with label, size, and keyboard interaction support. */\ninterface Props {\n modelValue?: boolean\n label?: string\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n reverse?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: false,\n disabled: false,\n size: 'md',\n reverse: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nfunction toggle() {\n if (!props.disabled) {\n emit('update:modelValue', !props.modelValue)\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n toggle()\n }\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-toggle',\n reverse ? 'mld-toggle--reverse' : '',\n disabled ? 'mld-toggle--disabled' : '',\n ]\"\n @click=\"toggle\"\n >\n <div\n role=\"switch\"\n :tabindex=\"disabled ? -1 : 0\"\n :aria-checked=\"modelValue\"\n :aria-label=\"label || 'Toggle'\"\n :class=\"[\n 'mld-toggle__track',\n `mld-toggle__track--${size}`,\n modelValue ? 'mld-toggle__track--on' : 'mld-toggle__track--off',\n ]\"\n @keydown=\"handleKeydown\"\n >\n <span\n :class=\"[\n 'mld-toggle__knob',\n `mld-toggle__knob--${size}`,\n modelValue ? `mld-toggle__knob--on-${size}` : 'mld-toggle__knob--off',\n ]\"\n />\n </div>\n <span\n v-if=\"label\"\n :class=\"['mld-toggle__label', `mld-toggle__label--${size}`]\"\n >\n {{ label }}\n </span>\n </div>\n</template>\n\n<style>\n@import '../styles/components/toggle.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a group of radio buttons in list or tile layout, horizontal or vertical. */\nimport type { RadioOption } from '../types'\n\ninterface Props {\n modelValue?: string | number\n options: RadioOption[]\n name: string\n disabled?: boolean\n direction?: 'horizontal' | 'vertical'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'list' | 'tile'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n direction: 'vertical',\n size: 'md',\n variant: 'list',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nfunction handleChange(value: string | number) {\n emit('update:modelValue', value)\n}\n</script>\n\n<template>\n <div\n :class=\"['mld-radio-group', `mld-radio-group--${props.direction}`, `mld-radio-group--${props.variant}`]\"\n role=\"radiogroup\"\n >\n <label\n v-for=\"option in props.options\"\n :key=\"String(option.value)\"\n :class=\"[\n 'mld-radio-option',\n props.modelValue === option.value ? 'mld-radio-option--selected' : '',\n (props.disabled || option.disabled) ? 'mld-radio-option--disabled' : '',\n ]\"\n >\n <template v-if=\"props.variant === 'list'\">\n <div :class=\"['mld-radio-option__input-wrapper', `mld-radio-option__input-wrapper--${props.size}`]\">\n <input\n type=\"radio\"\n :name=\"props.name\"\n :value=\"option.value\"\n :checked=\"props.modelValue === option.value\"\n :disabled=\"props.disabled || option.disabled\"\n :aria-describedby=\"option.description ? `${props.name}-${option.value}-desc` : undefined\"\n class=\"mld-radio-option__native\"\n @change=\"handleChange(option.value)\"\n />\n <div\n :class=\"[\n 'mld-radio-option__circle',\n `mld-radio-option__circle--${props.size}`,\n props.modelValue === option.value ? 'mld-radio-option__circle--checked' : '',\n ]\"\n >\n <div\n v-if=\"props.modelValue === option.value\"\n :class=\"['mld-radio-option__dot', `mld-radio-option__dot--${props.size}`]\"\n />\n </div>\n </div>\n <div class=\"mld-radio-option__content\">\n <span :class=\"['mld-radio-option__label', `mld-radio-option__label--${props.size}`]\">\n {{ option.label }}\n </span>\n <span\n v-if=\"option.description\"\n :id=\"`${props.name}-${option.value}-desc`\"\n :class=\"['mld-radio-option__description', `mld-radio-option__description--${props.size}`]\"\n >\n {{ option.description }}\n </span>\n </div>\n </template>\n\n <template v-else>\n <input\n type=\"radio\"\n :name=\"props.name\"\n :value=\"option.value\"\n :checked=\"props.modelValue === option.value\"\n :disabled=\"props.disabled || option.disabled\"\n :aria-describedby=\"option.description ? `${props.name}-${option.value}-desc` : undefined\"\n class=\"mld-radio-option__native\"\n @change=\"handleChange(option.value)\"\n />\n <div class=\"mld-radio-option__head\">\n <div\n :class=\"[\n 'mld-radio-option__circle',\n `mld-radio-option__circle--${props.size}`,\n props.modelValue === option.value ? 'mld-radio-option__circle--checked' : '',\n ]\"\n >\n <div\n v-if=\"props.modelValue === option.value\"\n :class=\"['mld-radio-option__dot', `mld-radio-option__dot--${props.size}`]\"\n />\n </div>\n <span class=\"mld-radio-option__title\">{{ option.label }}</span>\n </div>\n <span\n v-if=\"option.description\"\n :id=\"`${props.name}-${option.value}-desc`\"\n class=\"mld-radio-option__description\"\n >\n {{ option.description }}\n </span>\n </template>\n </label>\n </div>\n</template>\n\n<style>\n@import '../styles/components/radio-group.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a group of radio buttons in list or tile layout, horizontal or vertical. */\nimport type { RadioOption } from '../types'\n\ninterface Props {\n modelValue?: string | number\n options: RadioOption[]\n name: string\n disabled?: boolean\n direction?: 'horizontal' | 'vertical'\n size?: 'sm' | 'md' | 'lg'\n variant?: 'list' | 'tile'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n direction: 'vertical',\n size: 'md',\n variant: 'list',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | number]\n}>()\n\nfunction handleChange(value: string | number) {\n emit('update:modelValue', value)\n}\n</script>\n\n<template>\n <div\n :class=\"['mld-radio-group', `mld-radio-group--${props.direction}`, `mld-radio-group--${props.variant}`]\"\n role=\"radiogroup\"\n >\n <label\n v-for=\"option in props.options\"\n :key=\"String(option.value)\"\n :class=\"[\n 'mld-radio-option',\n props.modelValue === option.value ? 'mld-radio-option--selected' : '',\n (props.disabled || option.disabled) ? 'mld-radio-option--disabled' : '',\n ]\"\n >\n <template v-if=\"props.variant === 'list'\">\n <div :class=\"['mld-radio-option__input-wrapper', `mld-radio-option__input-wrapper--${props.size}`]\">\n <input\n type=\"radio\"\n :name=\"props.name\"\n :value=\"option.value\"\n :checked=\"props.modelValue === option.value\"\n :disabled=\"props.disabled || option.disabled\"\n :aria-describedby=\"option.description ? `${props.name}-${option.value}-desc` : undefined\"\n class=\"mld-radio-option__native\"\n @change=\"handleChange(option.value)\"\n />\n <div\n :class=\"[\n 'mld-radio-option__circle',\n `mld-radio-option__circle--${props.size}`,\n props.modelValue === option.value ? 'mld-radio-option__circle--checked' : '',\n ]\"\n >\n <div\n v-if=\"props.modelValue === option.value\"\n :class=\"['mld-radio-option__dot', `mld-radio-option__dot--${props.size}`]\"\n />\n </div>\n </div>\n <div class=\"mld-radio-option__content\">\n <span :class=\"['mld-radio-option__label', `mld-radio-option__label--${props.size}`]\">\n {{ option.label }}\n </span>\n <span\n v-if=\"option.description\"\n :id=\"`${props.name}-${option.value}-desc`\"\n :class=\"['mld-radio-option__description', `mld-radio-option__description--${props.size}`]\"\n >\n {{ option.description }}\n </span>\n </div>\n </template>\n\n <template v-else>\n <input\n type=\"radio\"\n :name=\"props.name\"\n :value=\"option.value\"\n :checked=\"props.modelValue === option.value\"\n :disabled=\"props.disabled || option.disabled\"\n :aria-describedby=\"option.description ? `${props.name}-${option.value}-desc` : undefined\"\n class=\"mld-radio-option__native\"\n @change=\"handleChange(option.value)\"\n />\n <div class=\"mld-radio-option__head\">\n <div\n :class=\"[\n 'mld-radio-option__circle',\n `mld-radio-option__circle--${props.size}`,\n props.modelValue === option.value ? 'mld-radio-option__circle--checked' : '',\n ]\"\n >\n <div\n v-if=\"props.modelValue === option.value\"\n :class=\"['mld-radio-option__dot', `mld-radio-option__dot--${props.size}`]\"\n />\n </div>\n <span class=\"mld-radio-option__title\">{{ option.label }}</span>\n </div>\n <span\n v-if=\"option.description\"\n :id=\"`${props.name}-${option.value}-desc`\"\n class=\"mld-radio-option__description\"\n >\n {{ option.description }}\n </span>\n </template>\n </label>\n </div>\n</template>\n\n<style>\n@import '../styles/components/radio-group.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a range slider with a filled track, optional value display, and size variants. */\nimport { computed, ref } from 'vue'\n\ninterface Props {\n modelValue?: number\n min?: number\n max?: number\n step?: number\n disabled?: boolean\n showValue?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n min: 0,\n max: 100,\n step: 1,\n disabled: false,\n showValue: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n}>()\n\nconst isDragging = ref(false)\n\nconst sizeConfig = computed(() => {\n switch (props.size) {\n case 'sm': return { height: 4, thumb: 14 }\n case 'lg': return { height: 10, thumb: 24 }\n default: return { height: 6, thumb: 18 }\n }\n})\n\nconst currentValue = computed(() => {\n return props.modelValue ?? props.min\n})\n\nconst percentage = computed(() => {\n const range = props.max - props.min\n if (range === 0) return 0\n return ((currentValue.value - props.min) / range) * 100\n})\n\n// Thumb-in-bounds offset (Radix UI pattern). Without this, the thumb's\n// center lands at left=0%/100% and overhangs the track by halfWidth at\n// each end. Linearly interpolating an inward push of halfWidth at percent=0\n// to halfWidth-pull at percent=100 keeps the thumb's *edges* — not its\n// center — aligned with the track edges.\nconst thumbPosition = computed(() => {\n const halfWidth = sizeConfig.value.thumb / 2\n const offsetPx = (halfWidth * (50 - percentage.value)) / 50\n return `calc(${percentage.value}% + ${offsetPx}px)`\n})\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', Number(target.value))\n}\n</script>\n\n<template>\n <div class=\"mld-slider\" :class=\"{ 'mld-slider--disabled': disabled }\">\n <div class=\"mld-slider__container\">\n <!-- Track background -->\n <div\n class=\"mld-slider__track\"\n :style=\"{ height: `${sizeConfig.height}px` }\"\n />\n\n <!-- Filled track — extends to the thumb's center so the two visually\n meet at every position (no gap at the right edge, no overshoot\n at the left). -->\n <div\n class=\"mld-slider__fill\"\n :style=\"{\n height: `${sizeConfig.height}px`,\n width: thumbPosition,\n }\"\n />\n\n <!-- Native range input -->\n <input\n type=\"range\"\n class=\"mld-slider__input\"\n :value=\"currentValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :aria-label=\"`Value: ${currentValue}`\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"currentValue\"\n @input=\"handleInput\"\n @mousedown=\"isDragging = true\"\n @mouseup=\"isDragging = false\"\n @touchstart.passive=\"isDragging = true\"\n @touchend=\"isDragging = false\"\n />\n\n <!-- Custom thumb — translateX(-50%) centers it on `left` -->\n <div\n class=\"mld-slider__thumb\"\n :class=\"{ 'mld-slider__thumb--dragging': isDragging }\"\n :style=\"{\n width: `${sizeConfig.thumb}px`,\n height: `${sizeConfig.thumb}px`,\n left: thumbPosition,\n }\"\n />\n </div>\n\n <!-- Value display -->\n <span v-if=\"showValue\" class=\"mld-slider__value\">\n {{ currentValue }}\n </span>\n </div>\n</template>\n\n<style>\n@import '../styles/components/slider.css';\n</style>\n","<script setup lang=\"ts\">\n/** Renders a range slider with a filled track, optional value display, and size variants. */\nimport { computed, ref } from 'vue'\n\ninterface Props {\n modelValue?: number\n min?: number\n max?: number\n step?: number\n disabled?: boolean\n showValue?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n min: 0,\n max: 100,\n step: 1,\n disabled: false,\n showValue: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number]\n}>()\n\nconst isDragging = ref(false)\n\nconst sizeConfig = computed(() => {\n switch (props.size) {\n case 'sm': return { height: 4, thumb: 14 }\n case 'lg': return { height: 10, thumb: 24 }\n default: return { height: 6, thumb: 18 }\n }\n})\n\nconst currentValue = computed(() => {\n return props.modelValue ?? props.min\n})\n\nconst percentage = computed(() => {\n const range = props.max - props.min\n if (range === 0) return 0\n return ((currentValue.value - props.min) / range) * 100\n})\n\n// Thumb-in-bounds offset (Radix UI pattern). Without this, the thumb's\n// center lands at left=0%/100% and overhangs the track by halfWidth at\n// each end. Linearly interpolating an inward push of halfWidth at percent=0\n// to halfWidth-pull at percent=100 keeps the thumb's *edges* — not its\n// center — aligned with the track edges.\nconst thumbPosition = computed(() => {\n const halfWidth = sizeConfig.value.thumb / 2\n const offsetPx = (halfWidth * (50 - percentage.value)) / 50\n return `calc(${percentage.value}% + ${offsetPx}px)`\n})\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', Number(target.value))\n}\n</script>\n\n<template>\n <div class=\"mld-slider\" :class=\"{ 'mld-slider--disabled': disabled }\">\n <div class=\"mld-slider__container\">\n <!-- Track background -->\n <div\n class=\"mld-slider__track\"\n :style=\"{ height: `${sizeConfig.height}px` }\"\n />\n\n <!-- Filled track — extends to the thumb's center so the two visually\n meet at every position (no gap at the right edge, no overshoot\n at the left). -->\n <div\n class=\"mld-slider__fill\"\n :style=\"{\n height: `${sizeConfig.height}px`,\n width: thumbPosition,\n }\"\n />\n\n <!-- Native range input -->\n <input\n type=\"range\"\n class=\"mld-slider__input\"\n :value=\"currentValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :aria-label=\"`Value: ${currentValue}`\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuenow=\"currentValue\"\n @input=\"handleInput\"\n @mousedown=\"isDragging = true\"\n @mouseup=\"isDragging = false\"\n @touchstart.passive=\"isDragging = true\"\n @touchend=\"isDragging = false\"\n />\n\n <!-- Custom thumb — translateX(-50%) centers it on `left` -->\n <div\n class=\"mld-slider__thumb\"\n :class=\"{ 'mld-slider__thumb--dragging': isDragging }\"\n :style=\"{\n width: `${sizeConfig.thumb}px`,\n height: `${sizeConfig.thumb}px`,\n left: thumbPosition,\n }\"\n />\n </div>\n\n <!-- Value display -->\n <span v-if=\"showValue\" class=\"mld-slider__value\">\n {{ currentValue }}\n </span>\n </div>\n</template>\n\n<style>\n@import '../styles/components/slider.css';\n</style>\n","<script setup lang=\"ts\">\n/** Lets users select multiple options from a checkbox list with optional selection cap. */\nimport { computed } from 'vue'\nimport type { MultiSelectOption, MultiSelectSize } from '../types'\n\ninterface Props {\n modelValue: (string | number)[]\n options: MultiSelectOption[]\n placeholder?: string\n disabled?: boolean\n maxSelections?: number\n size?: MultiSelectSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Select options...',\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: (string | number)[]]\n}>()\n\nconst canSelectMore = computed(() => {\n if (props.maxSelections === undefined) return true\n return props.modelValue.length < props.maxSelections\n})\n\nfunction isSelected(value: string | number): boolean {\n return props.modelValue.includes(value)\n}\n\nfunction toggleOption(option: MultiSelectOption) {\n if (props.disabled || option.disabled) return\n\n if (isSelected(option.value)) {\n emit('update:modelValue', props.modelValue.filter(v => v !== option.value))\n } else if (canSelectMore.value) {\n emit('update:modelValue', [...props.modelValue, option.value])\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent, option: MultiSelectOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n toggleOption(option)\n }\n}\n\nconst selectedLabels = computed(() => {\n return props.modelValue\n .map(v => props.options.find(o => o.value === v)?.label)\n .filter(Boolean)\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-multi-select',\n `mld-multi-select--${size}`,\n disabled ? 'mld-multi-select--disabled' : '',\n ]\"\n >\n <div\n v-if=\"modelValue.length === 0\"\n class=\"mld-multi-select__placeholder\"\n >\n {{ placeholder }}\n </div>\n\n <div class=\"mld-multi-select__options\" role=\"group\">\n <button\n v-for=\"option in options\"\n :key=\"String(option.value)\"\n type=\"button\"\n role=\"checkbox\"\n :aria-checked=\"isSelected(option.value)\"\n :disabled=\"disabled || option.disabled || (!isSelected(option.value) && !canSelectMore)\"\n :class=\"[\n 'mld-multi-select__chip',\n `mld-multi-select__chip--${size}`,\n isSelected(option.value) ? 'mld-multi-select__chip--active' : '',\n option.disabled ? 'mld-multi-select__chip--disabled' : '',\n !isSelected(option.value) && !canSelectMore ? 'mld-multi-select__chip--disabled' : '',\n ]\"\n @click=\"toggleOption(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mld-multi-select__chip-label\">{{ option.label }}</span>\n <svg\n v-if=\"isSelected(option.value)\"\n class=\"mld-multi-select__chip-check\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </button>\n </div>\n\n <div\n v-if=\"modelValue.length > 0\"\n class=\"mld-multi-select__summary\"\n >\n {{ selectedLabels.join(', ') }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/multi-select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Lets users select multiple options from a checkbox list with optional selection cap. */\nimport { computed } from 'vue'\nimport type { MultiSelectOption, MultiSelectSize } from '../types'\n\ninterface Props {\n modelValue: (string | number)[]\n options: MultiSelectOption[]\n placeholder?: string\n disabled?: boolean\n maxSelections?: number\n size?: MultiSelectSize\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Select options...',\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: (string | number)[]]\n}>()\n\nconst canSelectMore = computed(() => {\n if (props.maxSelections === undefined) return true\n return props.modelValue.length < props.maxSelections\n})\n\nfunction isSelected(value: string | number): boolean {\n return props.modelValue.includes(value)\n}\n\nfunction toggleOption(option: MultiSelectOption) {\n if (props.disabled || option.disabled) return\n\n if (isSelected(option.value)) {\n emit('update:modelValue', props.modelValue.filter(v => v !== option.value))\n } else if (canSelectMore.value) {\n emit('update:modelValue', [...props.modelValue, option.value])\n }\n}\n\nfunction handleKeydown(event: KeyboardEvent, option: MultiSelectOption) {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n toggleOption(option)\n }\n}\n\nconst selectedLabels = computed(() => {\n return props.modelValue\n .map(v => props.options.find(o => o.value === v)?.label)\n .filter(Boolean)\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-multi-select',\n `mld-multi-select--${size}`,\n disabled ? 'mld-multi-select--disabled' : '',\n ]\"\n >\n <div\n v-if=\"modelValue.length === 0\"\n class=\"mld-multi-select__placeholder\"\n >\n {{ placeholder }}\n </div>\n\n <div class=\"mld-multi-select__options\" role=\"group\">\n <button\n v-for=\"option in options\"\n :key=\"String(option.value)\"\n type=\"button\"\n role=\"checkbox\"\n :aria-checked=\"isSelected(option.value)\"\n :disabled=\"disabled || option.disabled || (!isSelected(option.value) && !canSelectMore)\"\n :class=\"[\n 'mld-multi-select__chip',\n `mld-multi-select__chip--${size}`,\n isSelected(option.value) ? 'mld-multi-select__chip--active' : '',\n option.disabled ? 'mld-multi-select__chip--disabled' : '',\n !isSelected(option.value) && !canSelectMore ? 'mld-multi-select__chip--disabled' : '',\n ]\"\n @click=\"toggleOption(option)\"\n @keydown=\"handleKeydown($event, option)\"\n >\n <span class=\"mld-multi-select__chip-label\">{{ option.label }}</span>\n <svg\n v-if=\"isSelected(option.value)\"\n class=\"mld-multi-select__chip-check\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M20 6 9 17l-5-5\" />\n </svg>\n </button>\n </div>\n\n <div\n v-if=\"modelValue.length > 0\"\n class=\"mld-multi-select__summary\"\n >\n {{ selectedLabels.join(', ') }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/multi-select.css';\n</style>\n","<script setup lang=\"ts\">\n/** Input that opens a calendar dropdown for selecting a single date, with clearable support. */\nimport { ref, computed, watch, onMounted, onUnmounted } from 'vue'\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 clearable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n clearable: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | undefined]\n}>()\n\nconst isOpen = ref(false)\nconst containerRef = ref<HTMLDivElement>()\nconst inputRef = ref<HTMLDivElement>()\nconst dropdownRef = ref<HTMLDivElement>()\nconst dropdownStyle = ref<Record<string, string>>({})\nconst currentMonth = ref(new Date())\n\nconst selectedDate = computed(() => {\n if (!props.modelValue) return null\n const date = new Date(props.modelValue + 'T00:00:00')\n return isNaN(date.getTime()) ? null : date\n})\n\nconst displayValue = computed(() => {\n if (!selectedDate.value) return ''\n return selectedDate.value.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n})\n\nconst monthYear = computed(() => {\n return currentMonth.value.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n })\n})\n\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\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 // Previous month days\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 // Current month days\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 // Next month days\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\nfunction isDateDisabled(date: Date): boolean {\n if (props.min) {\n const minDate = new Date(props.min + 'T00:00:00')\n if (date < minDate) return true\n }\n if (props.max) {\n const maxDate = new Date(props.max + 'T00:00:00')\n if (date > maxDate) return true\n }\n return false\n}\n\nfunction isSameDay(date1: Date, date2: Date | null): boolean {\n if (!date2) return false\n return date1.toDateString() === date2.toDateString()\n}\n\nfunction isToday(date: Date): boolean {\n return date.toDateString() === new Date().toDateString()\n}\n\nfunction formatDateValue(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nfunction selectDate(day: { date: Date; isDisabled: boolean }) {\n if (day.isDisabled || props.disabled) return\n emit('update:modelValue', formatDateValue(day.date))\n isOpen.value = false\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 goToToday() {\n const today = new Date()\n currentMonth.value = new Date(today.getFullYear(), today.getMonth(), 1)\n if (!isDateDisabled(today)) {\n emit('update:modelValue', formatDateValue(today))\n isOpen.value = false\n }\n}\n\nfunction clear() {\n emit('update:modelValue', undefined)\n isOpen.value = false\n}\n\nfunction toggleCalendar() {\n if (props.disabled) return\n isOpen.value = !isOpen.value\n}\n\nfunction updateDropdownPosition() {\n if (!inputRef.value) return\n const rect = inputRef.value.getBoundingClientRect()\n dropdownStyle.value = {\n position: 'fixed',\n top: `${rect.bottom + 4}px`,\n left: `${rect.left}px`,\n width: `${Math.max(rect.width, 288)}px`,\n zIndex: '9999',\n }\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n const target = event.target as Node\n if (containerRef.value?.contains(target)) return\n if (dropdownRef.value?.contains(target)) return\n isOpen.value = false\n}\n\nfunction handleScrollOrResize() {\n if (isOpen.value) isOpen.value = false\n}\n\nwatch(isOpen, (open) => {\n if (open) {\n updateDropdownPosition()\n if (selectedDate.value) {\n currentMonth.value = new Date(\n selectedDate.value.getFullYear(),\n selectedDate.value.getMonth(),\n 1\n )\n }\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n window.addEventListener('scroll', handleScrollOrResize, true)\n window.addEventListener('resize', handleScrollOrResize)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n window.removeEventListener('scroll', handleScrollOrResize, true)\n window.removeEventListener('resize', handleScrollOrResize)\n})\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"mld-date-picker\">\n <div ref=\"inputRef\" class=\"mld-date-picker__input-wrapper\">\n <input\n type=\"text\"\n readonly\n :value=\"displayValue\"\n :placeholder=\"placeholder || 'Select date'\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-date-picker__input',\n `mld-date-picker__input--${size}`,\n error ? 'mld-date-picker__input--error' : '',\n disabled ? 'mld-date-picker__input--disabled' : '',\n ]\"\n @click=\"toggleCalendar\"\n />\n <div class=\"mld-date-picker__icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M8 2v4\" /><path d=\"M16 2v4\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" /><path d=\"M3 10h18\" />\n </svg>\n </div>\n </div>\n\n <Teleport to=\"body\">\n <Transition\n enter-active-class=\"mld-date-picker__dropdown-enter-active\"\n enter-from-class=\"mld-date-picker__dropdown-enter-from\"\n enter-to-class=\"mld-date-picker__dropdown-enter-to\"\n leave-active-class=\"mld-date-picker__dropdown-leave-active\"\n leave-from-class=\"mld-date-picker__dropdown-leave-from\"\n leave-to-class=\"mld-date-picker__dropdown-leave-to\"\n >\n <div\n v-if=\"isOpen\"\n ref=\"dropdownRef\"\n class=\"mld-date-picker__dropdown\"\n :style=\"dropdownStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Date picker\"\n >\n <div class=\"mld-date-picker__header\">\n <button\n type=\"button\"\n aria-label=\"Previous month\"\n class=\"mld-date-picker__nav-btn\"\n @click=\"prevMonth\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <span class=\"mld-date-picker__month-year\">{{ monthYear }}</span>\n <button\n type=\"button\"\n aria-label=\"Next month\"\n class=\"mld-date-picker__nav-btn\"\n @click=\"nextMonth\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <div class=\"mld-date-picker__weekdays\">\n <div\n v-for=\"day in weekDays\"\n :key=\"day\"\n class=\"mld-date-picker__weekday\"\n >\n {{ day }}\n </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 :aria-label=\"day.date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })\"\n :aria-selected=\"isSameDay(day.date, selectedDate)\"\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\n <div class=\"mld-date-picker__footer\">\n <button\n type=\"button\"\n class=\"mld-date-picker__footer-btn mld-date-picker__today-btn\"\n @click=\"goToToday\"\n >\n Today\n </button>\n <button\n v-if=\"clearable && modelValue\"\n type=\"button\"\n class=\"mld-date-picker__footer-btn mld-date-picker__clear-btn\"\n @click=\"clear\"\n >\n Clear\n </button>\n </div>\n </div>\n </Transition>\n </Teleport>\n </div>\n</template>\n\n<style>\n@import '../styles/components/date-picker.css';\n</style>\n","<script setup lang=\"ts\">\n/** Input that opens a calendar dropdown for selecting a single date, with clearable support. */\nimport { ref, computed, watch, onMounted, onUnmounted } from 'vue'\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 clearable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n error: false,\n size: 'md',\n clearable: true,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | undefined]\n}>()\n\nconst isOpen = ref(false)\nconst containerRef = ref<HTMLDivElement>()\nconst inputRef = ref<HTMLDivElement>()\nconst dropdownRef = ref<HTMLDivElement>()\nconst dropdownStyle = ref<Record<string, string>>({})\nconst currentMonth = ref(new Date())\n\nconst selectedDate = computed(() => {\n if (!props.modelValue) return null\n const date = new Date(props.modelValue + 'T00:00:00')\n return isNaN(date.getTime()) ? null : date\n})\n\nconst displayValue = computed(() => {\n if (!selectedDate.value) return ''\n return selectedDate.value.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n})\n\nconst monthYear = computed(() => {\n return currentMonth.value.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n })\n})\n\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\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 // Previous month days\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 // Current month days\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 // Next month days\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\nfunction isDateDisabled(date: Date): boolean {\n if (props.min) {\n const minDate = new Date(props.min + 'T00:00:00')\n if (date < minDate) return true\n }\n if (props.max) {\n const maxDate = new Date(props.max + 'T00:00:00')\n if (date > maxDate) return true\n }\n return false\n}\n\nfunction isSameDay(date1: Date, date2: Date | null): boolean {\n if (!date2) return false\n return date1.toDateString() === date2.toDateString()\n}\n\nfunction isToday(date: Date): boolean {\n return date.toDateString() === new Date().toDateString()\n}\n\nfunction formatDateValue(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nfunction selectDate(day: { date: Date; isDisabled: boolean }) {\n if (day.isDisabled || props.disabled) return\n emit('update:modelValue', formatDateValue(day.date))\n isOpen.value = false\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 goToToday() {\n const today = new Date()\n currentMonth.value = new Date(today.getFullYear(), today.getMonth(), 1)\n if (!isDateDisabled(today)) {\n emit('update:modelValue', formatDateValue(today))\n isOpen.value = false\n }\n}\n\nfunction clear() {\n emit('update:modelValue', undefined)\n isOpen.value = false\n}\n\nfunction toggleCalendar() {\n if (props.disabled) return\n isOpen.value = !isOpen.value\n}\n\nfunction updateDropdownPosition() {\n if (!inputRef.value) return\n const rect = inputRef.value.getBoundingClientRect()\n dropdownStyle.value = {\n position: 'fixed',\n top: `${rect.bottom + 4}px`,\n left: `${rect.left}px`,\n width: `${Math.max(rect.width, 288)}px`,\n zIndex: '9999',\n }\n}\n\nfunction handleClickOutside(event: MouseEvent) {\n const target = event.target as Node\n if (containerRef.value?.contains(target)) return\n if (dropdownRef.value?.contains(target)) return\n isOpen.value = false\n}\n\nfunction handleScrollOrResize() {\n if (isOpen.value) isOpen.value = false\n}\n\nwatch(isOpen, (open) => {\n if (open) {\n updateDropdownPosition()\n if (selectedDate.value) {\n currentMonth.value = new Date(\n selectedDate.value.getFullYear(),\n selectedDate.value.getMonth(),\n 1\n )\n }\n }\n})\n\nonMounted(() => {\n document.addEventListener('click', handleClickOutside)\n window.addEventListener('scroll', handleScrollOrResize, true)\n window.addEventListener('resize', handleScrollOrResize)\n})\n\nonUnmounted(() => {\n document.removeEventListener('click', handleClickOutside)\n window.removeEventListener('scroll', handleScrollOrResize, true)\n window.removeEventListener('resize', handleScrollOrResize)\n})\n</script>\n\n<template>\n <div ref=\"containerRef\" class=\"mld-date-picker\">\n <div ref=\"inputRef\" class=\"mld-date-picker__input-wrapper\">\n <input\n type=\"text\"\n readonly\n :value=\"displayValue\"\n :placeholder=\"placeholder || 'Select date'\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-date-picker__input',\n `mld-date-picker__input--${size}`,\n error ? 'mld-date-picker__input--error' : '',\n disabled ? 'mld-date-picker__input--disabled' : '',\n ]\"\n @click=\"toggleCalendar\"\n />\n <div class=\"mld-date-picker__icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M8 2v4\" /><path d=\"M16 2v4\" /><rect width=\"18\" height=\"18\" x=\"3\" y=\"4\" rx=\"2\" /><path d=\"M3 10h18\" />\n </svg>\n </div>\n </div>\n\n <Teleport to=\"body\">\n <Transition\n enter-active-class=\"mld-date-picker__dropdown-enter-active\"\n enter-from-class=\"mld-date-picker__dropdown-enter-from\"\n enter-to-class=\"mld-date-picker__dropdown-enter-to\"\n leave-active-class=\"mld-date-picker__dropdown-leave-active\"\n leave-from-class=\"mld-date-picker__dropdown-leave-from\"\n leave-to-class=\"mld-date-picker__dropdown-leave-to\"\n >\n <div\n v-if=\"isOpen\"\n ref=\"dropdownRef\"\n class=\"mld-date-picker__dropdown\"\n :style=\"dropdownStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Date picker\"\n >\n <div class=\"mld-date-picker__header\">\n <button\n type=\"button\"\n aria-label=\"Previous month\"\n class=\"mld-date-picker__nav-btn\"\n @click=\"prevMonth\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m15 18-6-6 6-6\" />\n </svg>\n </button>\n <span class=\"mld-date-picker__month-year\">{{ monthYear }}</span>\n <button\n type=\"button\"\n aria-label=\"Next month\"\n class=\"mld-date-picker__nav-btn\"\n @click=\"nextMonth\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"m9 18 6-6-6-6\" />\n </svg>\n </button>\n </div>\n\n <div class=\"mld-date-picker__weekdays\">\n <div\n v-for=\"day in weekDays\"\n :key=\"day\"\n class=\"mld-date-picker__weekday\"\n >\n {{ day }}\n </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 :aria-label=\"day.date.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })\"\n :aria-selected=\"isSameDay(day.date, selectedDate)\"\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\n <div class=\"mld-date-picker__footer\">\n <button\n type=\"button\"\n class=\"mld-date-picker__footer-btn mld-date-picker__today-btn\"\n @click=\"goToToday\"\n >\n Today\n </button>\n <button\n v-if=\"clearable && modelValue\"\n type=\"button\"\n class=\"mld-date-picker__footer-btn mld-date-picker__clear-btn\"\n @click=\"clear\"\n >\n Clear\n </button>\n </div>\n </div>\n </Transition>\n </Teleport>\n </div>\n</template>\n\n<style>\n@import '../styles/components/date-picker.css';\n</style>\n","import type { TimeRange } from '../types/components'\n\nexport function parseTime(time: string): { hour: number; minute: number } {\n const [h, m] = time.split(':').map(Number)\n return { hour: h, minute: m }\n}\n\nexport function formatTime(hour: number, minute: number, format: '12h' | '24h' = '24h'): string {\n if (format === '24h') {\n return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}`\n }\n const period = hour >= 12 ? 'PM' : 'AM'\n const h12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour\n return `${h12}:${String(minute).padStart(2, '0')} ${period}`\n}\n\nexport function generateTimeSlots(start: string, end: string, stepMinutes: number): string[] {\n const slots: string[] = []\n const s = parseTime(start)\n const e = parseTime(end)\n const startMin = s.hour * 60 + s.minute\n const endMin = e.hour * 60 + e.minute\n\n for (let m = startMin; m <= endMin; m += stepMinutes) {\n const hour = Math.floor(m / 60)\n const minute = m % 60\n if (hour >= 24) break\n slots.push(formatTime(hour, minute))\n }\n return slots\n}\n\nexport function rangesOverlap(a: TimeRange, b: TimeRange): boolean {\n const aStart = toMinutes(a.start)\n const aEnd = toMinutes(a.end)\n const bStart = toMinutes(b.start)\n const bEnd = toMinutes(b.end)\n return aStart < bEnd && bStart < aEnd\n}\n\nexport function durationMinutes(start: string, end: string): number {\n return toMinutes(end) - toMinutes(start)\n}\n\nexport function formatDuration(minutes: number): string {\n if (minutes < 0) return '-' + formatDuration(-minutes)\n const h = Math.floor(minutes / 60)\n const m = minutes % 60\n if (h === 0) return `${m}m`\n if (m === 0) return `${h}h`\n return `${h}h ${m}m`\n}\n\nexport function isTimeInRange(time: string, start: string, end: string): boolean {\n const t = toMinutes(time)\n return t >= toMinutes(start) && t <= toMinutes(end)\n}\n\nexport function findAvailableSlots(\n dayStart: string,\n dayEnd: string,\n occupied: TimeRange[],\n minDuration: number,\n): TimeRange[] {\n const sorted = [...occupied].sort((a, b) => toMinutes(a.start) - toMinutes(b.start))\n const available: TimeRange[] = []\n let cursor = toMinutes(dayStart)\n const end = toMinutes(dayEnd)\n\n for (const slot of sorted) {\n const slotStart = toMinutes(slot.start)\n const slotEnd = toMinutes(slot.end)\n if (slotStart > cursor && slotStart - cursor >= minDuration) {\n available.push({ start: fromMinutes(cursor), end: fromMinutes(slotStart) })\n }\n cursor = Math.max(cursor, slotEnd)\n }\n\n if (end > cursor && end - cursor >= minDuration) {\n available.push({ start: fromMinutes(cursor), end: fromMinutes(end) })\n }\n\n return available\n}\n\nexport function snapToSlot(time: string, stepMinutes: number): string {\n const total = toMinutes(time)\n const snapped = Math.round(total / stepMinutes) * stepMinutes\n return fromMinutes(snapped)\n}\n\nexport function addMinutes(time: string, minutes: number): string {\n const total = toMinutes(time) + minutes\n return fromMinutes(Math.max(0, Math.min(total, 24 * 60 - 1)))\n}\n\nexport function compareTime(a: string, b: string): number {\n const ma = toMinutes(a)\n const mb = toMinutes(b)\n if (ma < mb) return -1\n if (ma > mb) return 1\n return 0\n}\n\nfunction toMinutes(time: string): number {\n const { hour, minute } = parseTime(time)\n return hour * 60 + minute\n}\n\nfunction fromMinutes(total: number): string {\n const hour = Math.floor(total / 60) % 24\n const minute = total % 60\n return formatTime(hour, minute)\n}\n\n/** Time utility helpers for parsing, formatting, comparing, and snapping HH:MM strings. */\nexport function useTimeUtils() {\n return {\n parseTime,\n formatTime,\n generateTimeSlots,\n rangesOverlap,\n durationMinutes,\n formatDuration,\n isTimeInRange,\n findAvailableSlots,\n snapToSlot,\n addMinutes,\n compareTime,\n }\n}\n","<script setup lang=\"ts\">\n/** Input that opens a scrollable time-slot dropdown in 12h or 24h format, with min/max range. */\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'\nimport { generateTimeSlots, formatTime, parseTime, compareTime } 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 step?: number\n format?: '12h' | '24h'\n clearable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Select time',\n disabled: false,\n error: false,\n size: 'md',\n step: 15,\n format: '24h',\n clearable: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | undefined]\n}>()\n\nconst isOpen = ref(false)\nconst containerRef = ref<HTMLDivElement>()\nconst listRef = ref<HTMLUListElement>()\nconst highlightedIndex = ref(-1)\n\nconst slots = computed(() => {\n const start = props.min ?? '00:00'\n const end = props.max ?? '23:59'\n return generateTimeSlots(start, end, props.step)\n})\n\nconst displayValue = computed(() => {\n if (!props.modelValue) return ''\n const { hour, minute } = parseTime(props.modelValue)\n return formatTime(hour, minute, props.format)\n})\n\nfunction isSlotDisabled(slot: string): boolean {\n if (props.min && compareTime(slot, props.min) < 0) return true\n if (props.max && compareTime(slot, props.max) > 0) return true\n return false\n}\n\nfunction selectSlot(slot: string) {\n if (isSlotDisabled(slot) || props.disabled) return\n emit('update:modelValue', slot)\n isOpen.value = false\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\nfunction scrollToActiveSlot() {\n nextTick(() => {\n if (!listRef.value) return\n const activeEl = listRef.value.querySelector('.mld-time-picker__slot--active') as HTMLElement | null\n if (activeEl) {\n activeEl.scrollIntoView({ block: 'center' })\n } else if (props.modelValue) {\n // Scroll to nearest slot\n const nearestIndex = findNearestSlotIndex(props.modelValue)\n if (nearestIndex >= 0) {\n const children = listRef.value.children\n if (children[nearestIndex]) {\n (children[nearestIndex] as HTMLElement).scrollIntoView({ block: 'center' })\n }\n }\n }\n })\n}\n\nfunction findNearestSlotIndex(time: string): number {\n if (slots.value.length === 0) return -1\n let bestIndex = 0\n let bestDiff = Infinity\n for (let i = 0; i < slots.value.length; i++) {\n const { hour: h1, minute: m1 } = parseTime(slots.value[i])\n const { hour: h2, minute: m2 } = parseTime(time)\n const minuteDiff = Math.abs((h1 * 60 + m1) - (h2 * 60 + m2))\n if (minuteDiff < bestDiff) {\n bestDiff = minuteDiff\n bestIndex = i\n }\n }\n return bestIndex\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!isOpen.value) {\n if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {\n event.preventDefault()\n isOpen.value = true\n return\n }\n return\n }\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n let next = highlightedIndex.value + 1\n while (next < slots.value.length && isSlotDisabled(slots.value[next])) next++\n if (next < slots.value.length) {\n highlightedIndex.value = next\n scrollToHighlighted()\n }\n break\n }\n case 'ArrowUp': {\n event.preventDefault()\n let prev = highlightedIndex.value - 1\n while (prev >= 0 && isSlotDisabled(slots.value[prev])) prev--\n if (prev >= 0) {\n highlightedIndex.value = prev\n scrollToHighlighted()\n }\n break\n }\n case 'Enter': {\n event.preventDefault()\n if (highlightedIndex.value >= 0 && highlightedIndex.value < slots.value.length) {\n selectSlot(slots.value[highlightedIndex.value])\n }\n break\n }\n case 'Escape': {\n event.preventDefault()\n isOpen.value = false\n break\n }\n }\n}\n\nfunction scrollToHighlighted() {\n nextTick(() => {\n if (!listRef.value) return\n const children = listRef.value.children\n if (children[highlightedIndex.value]) {\n (children[highlightedIndex.value] as HTMLElement).scrollIntoView({ block: 'nearest' })\n }\n })\n}\n\nwatch(isOpen, (open) => {\n if (open) {\n // Set highlighted index to current value or nearest slot\n if (props.modelValue) {\n const idx = slots.value.indexOf(props.modelValue)\n highlightedIndex.value = idx >= 0 ? idx : findNearestSlotIndex(props.modelValue)\n } else {\n highlightedIndex.value = 0\n }\n scrollToActiveSlot()\n } else {\n highlightedIndex.value = -1\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-time-picker\" @keydown=\"handleKeydown\">\n <div class=\"mld-time-picker__input-wrapper\">\n <input\n type=\"text\"\n readonly\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-time-picker__input',\n `mld-time-picker__input--${size}`,\n error ? 'mld-time-picker__input--error' : '',\n disabled ? 'mld-time-picker__input--disabled' : '',\n ]\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen\"\n @click=\"toggleDropdown\"\n />\n <button\n v-if=\"clearable && modelValue\"\n type=\"button\"\n class=\"mld-time-picker__clear-btn\"\n aria-label=\"Clear time\"\n @click.stop=\"clear\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n <div class=\"mld-time-picker__icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 6v6l4 2\" />\n </svg>\n </div>\n </div>\n\n <Transition\n enter-active-class=\"mld-time-picker__dropdown-enter-active\"\n enter-from-class=\"mld-time-picker__dropdown-enter-from\"\n enter-to-class=\"mld-time-picker__dropdown-enter-to\"\n leave-active-class=\"mld-time-picker__dropdown-leave-active\"\n leave-from-class=\"mld-time-picker__dropdown-leave-from\"\n leave-to-class=\"mld-time-picker__dropdown-leave-to\"\n >\n <div\n v-if=\"isOpen\"\n class=\"mld-time-picker__dropdown\"\n >\n <ul\n ref=\"listRef\"\n class=\"mld-time-picker__list\"\n role=\"listbox\"\n aria-label=\"Available times\"\n >\n <li\n v-for=\"(slot, index) in slots\"\n :key=\"slot\"\n role=\"option\"\n :aria-selected=\"slot === modelValue\"\n :aria-disabled=\"isSlotDisabled(slot)\"\n :class=\"[\n 'mld-time-picker__slot',\n slot === modelValue ? 'mld-time-picker__slot--active' : '',\n isSlotDisabled(slot) ? 'mld-time-picker__slot--disabled' : '',\n index === highlightedIndex && !isSlotDisabled(slot) ? 'mld-time-picker__slot--highlighted' : '',\n ]\"\n @click=\"selectSlot(slot)\"\n >\n {{ formatTime(parseTime(slot).hour, parseTime(slot).minute, format) }}\n </li>\n </ul>\n </div>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/time-picker.css';\n</style>\n","<script setup lang=\"ts\">\n/** Input that opens a scrollable time-slot dropdown in 12h or 24h format, with min/max range. */\nimport { ref, computed, watch, nextTick, onMounted, onUnmounted } from 'vue'\nimport { generateTimeSlots, formatTime, parseTime, compareTime } 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 step?: number\n format?: '12h' | '24h'\n clearable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n placeholder: 'Select time',\n disabled: false,\n error: false,\n size: 'md',\n step: 15,\n format: '24h',\n clearable: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | undefined]\n}>()\n\nconst isOpen = ref(false)\nconst containerRef = ref<HTMLDivElement>()\nconst listRef = ref<HTMLUListElement>()\nconst highlightedIndex = ref(-1)\n\nconst slots = computed(() => {\n const start = props.min ?? '00:00'\n const end = props.max ?? '23:59'\n return generateTimeSlots(start, end, props.step)\n})\n\nconst displayValue = computed(() => {\n if (!props.modelValue) return ''\n const { hour, minute } = parseTime(props.modelValue)\n return formatTime(hour, minute, props.format)\n})\n\nfunction isSlotDisabled(slot: string): boolean {\n if (props.min && compareTime(slot, props.min) < 0) return true\n if (props.max && compareTime(slot, props.max) > 0) return true\n return false\n}\n\nfunction selectSlot(slot: string) {\n if (isSlotDisabled(slot) || props.disabled) return\n emit('update:modelValue', slot)\n isOpen.value = false\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\nfunction scrollToActiveSlot() {\n nextTick(() => {\n if (!listRef.value) return\n const activeEl = listRef.value.querySelector('.mld-time-picker__slot--active') as HTMLElement | null\n if (activeEl) {\n activeEl.scrollIntoView({ block: 'center' })\n } else if (props.modelValue) {\n // Scroll to nearest slot\n const nearestIndex = findNearestSlotIndex(props.modelValue)\n if (nearestIndex >= 0) {\n const children = listRef.value.children\n if (children[nearestIndex]) {\n (children[nearestIndex] as HTMLElement).scrollIntoView({ block: 'center' })\n }\n }\n }\n })\n}\n\nfunction findNearestSlotIndex(time: string): number {\n if (slots.value.length === 0) return -1\n let bestIndex = 0\n let bestDiff = Infinity\n for (let i = 0; i < slots.value.length; i++) {\n const { hour: h1, minute: m1 } = parseTime(slots.value[i])\n const { hour: h2, minute: m2 } = parseTime(time)\n const minuteDiff = Math.abs((h1 * 60 + m1) - (h2 * 60 + m2))\n if (minuteDiff < bestDiff) {\n bestDiff = minuteDiff\n bestIndex = i\n }\n }\n return bestIndex\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (!isOpen.value) {\n if (event.key === 'Enter' || event.key === ' ' || event.key === 'ArrowDown') {\n event.preventDefault()\n isOpen.value = true\n return\n }\n return\n }\n\n switch (event.key) {\n case 'ArrowDown': {\n event.preventDefault()\n let next = highlightedIndex.value + 1\n while (next < slots.value.length && isSlotDisabled(slots.value[next])) next++\n if (next < slots.value.length) {\n highlightedIndex.value = next\n scrollToHighlighted()\n }\n break\n }\n case 'ArrowUp': {\n event.preventDefault()\n let prev = highlightedIndex.value - 1\n while (prev >= 0 && isSlotDisabled(slots.value[prev])) prev--\n if (prev >= 0) {\n highlightedIndex.value = prev\n scrollToHighlighted()\n }\n break\n }\n case 'Enter': {\n event.preventDefault()\n if (highlightedIndex.value >= 0 && highlightedIndex.value < slots.value.length) {\n selectSlot(slots.value[highlightedIndex.value])\n }\n break\n }\n case 'Escape': {\n event.preventDefault()\n isOpen.value = false\n break\n }\n }\n}\n\nfunction scrollToHighlighted() {\n nextTick(() => {\n if (!listRef.value) return\n const children = listRef.value.children\n if (children[highlightedIndex.value]) {\n (children[highlightedIndex.value] as HTMLElement).scrollIntoView({ block: 'nearest' })\n }\n })\n}\n\nwatch(isOpen, (open) => {\n if (open) {\n // Set highlighted index to current value or nearest slot\n if (props.modelValue) {\n const idx = slots.value.indexOf(props.modelValue)\n highlightedIndex.value = idx >= 0 ? idx : findNearestSlotIndex(props.modelValue)\n } else {\n highlightedIndex.value = 0\n }\n scrollToActiveSlot()\n } else {\n highlightedIndex.value = -1\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-time-picker\" @keydown=\"handleKeydown\">\n <div class=\"mld-time-picker__input-wrapper\">\n <input\n type=\"text\"\n readonly\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-time-picker__input',\n `mld-time-picker__input--${size}`,\n error ? 'mld-time-picker__input--error' : '',\n disabled ? 'mld-time-picker__input--disabled' : '',\n ]\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n :aria-expanded=\"isOpen\"\n @click=\"toggleDropdown\"\n />\n <button\n v-if=\"clearable && modelValue\"\n type=\"button\"\n class=\"mld-time-picker__clear-btn\"\n aria-label=\"Clear time\"\n @click.stop=\"clear\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n <div class=\"mld-time-picker__icon\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\" /><path d=\"M12 6v6l4 2\" />\n </svg>\n </div>\n </div>\n\n <Transition\n enter-active-class=\"mld-time-picker__dropdown-enter-active\"\n enter-from-class=\"mld-time-picker__dropdown-enter-from\"\n enter-to-class=\"mld-time-picker__dropdown-enter-to\"\n leave-active-class=\"mld-time-picker__dropdown-leave-active\"\n leave-from-class=\"mld-time-picker__dropdown-leave-from\"\n leave-to-class=\"mld-time-picker__dropdown-leave-to\"\n >\n <div\n v-if=\"isOpen\"\n class=\"mld-time-picker__dropdown\"\n >\n <ul\n ref=\"listRef\"\n class=\"mld-time-picker__list\"\n role=\"listbox\"\n aria-label=\"Available times\"\n >\n <li\n v-for=\"(slot, index) in slots\"\n :key=\"slot\"\n role=\"option\"\n :aria-selected=\"slot === modelValue\"\n :aria-disabled=\"isSlotDisabled(slot)\"\n :class=\"[\n 'mld-time-picker__slot',\n slot === modelValue ? 'mld-time-picker__slot--active' : '',\n isSlotDisabled(slot) ? 'mld-time-picker__slot--disabled' : '',\n index === highlightedIndex && !isSlotDisabled(slot) ? 'mld-time-picker__slot--highlighted' : '',\n ]\"\n @click=\"selectSlot(slot)\"\n >\n {{ formatTime(parseTime(slot).hour, parseTime(slot).minute, format) }}\n </li>\n </ul>\n </div>\n </Transition>\n </div>\n</template>\n\n<style>\n@import '../styles/components/time-picker.css';\n</style>\n","<script setup lang=\"ts\">\n/** Lets users type and add tags with autocomplete suggestions, categories, and a max-tag cap. */\nimport { ref, computed } from 'vue'\n\ninterface TagSuggestion {\n value: string\n count?: number\n}\n\ninterface TagCategory {\n name: string\n prefix: string\n color?: 'primary' | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n}\n\ninterface Props {\n modelValue?: string[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n maxTags?: number\n allowDuplicates?: boolean\n suggestions?: string[] | TagSuggestion[]\n categories?: TagCategory[]\n activeCategory?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n disabled: false,\n error: false,\n size: 'md',\n allowDuplicates: false,\n suggestions: () => [],\n categories: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string[]]\n 'update:activeCategory': [prefix: string]\n}>()\n\nconst inputValue = ref('')\nconst inputRef = ref<HTMLInputElement>()\nconst dropdownOpen = ref(false)\nconst highlightedIndex = ref(-1)\n\nconst canAddMore = computed(() => {\n if (props.maxTags === undefined) return true\n return props.modelValue.length < props.maxTags\n})\n\nconst normalizedSuggestions = computed<TagSuggestion[]>(() => {\n return props.suggestions.map(s => typeof s === 'string' ? { value: s } : s)\n})\n\nconst filteredSuggestions = computed<TagSuggestion[]>(() => {\n const q = inputValue.value.trim().toLowerCase()\n if (!q) return []\n return normalizedSuggestions.value\n .filter(s => s.value.toLowerCase().startsWith(q) && !props.modelValue.includes(s.value))\n .slice(0, 8)\n})\n\nfunction categoryFor(tag: string): TagCategory | undefined {\n const colonIdx = tag.indexOf(':')\n if (colonIdx === -1) return undefined\n const prefix = tag.slice(0, colonIdx)\n return props.categories.find(c => c.prefix === prefix)\n}\n\nfunction addTag(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (!canAddMore.value) return\n\n // If a category is active and user didn't already type a prefix, prepend it\n let final = trimmed\n if (props.activeCategory && !trimmed.includes(':')) {\n final = `${props.activeCategory}:${trimmed}`\n }\n\n if (!props.allowDuplicates && props.modelValue.includes(final)) return\n\n emit('update:modelValue', [...props.modelValue, final])\n inputValue.value = ''\n dropdownOpen.value = false\n highlightedIndex.value = -1\n}\n\nfunction removeTag(index: number) {\n if (props.disabled) return\n const newTags = [...props.modelValue]\n newTags.splice(index, 1)\n emit('update:modelValue', newTags)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (event.key === 'ArrowDown' && filteredSuggestions.value.length) {\n event.preventDefault()\n highlightedIndex.value = Math.min(highlightedIndex.value + 1, filteredSuggestions.value.length - 1)\n dropdownOpen.value = true\n return\n }\n if (event.key === 'ArrowUp' && filteredSuggestions.value.length) {\n event.preventDefault()\n highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)\n return\n }\n if (event.key === 'Enter' || event.key === ',') {\n event.preventDefault()\n if (highlightedIndex.value >= 0 && filteredSuggestions.value[highlightedIndex.value]) {\n addTag(filteredSuggestions.value[highlightedIndex.value].value)\n } else {\n addTag(inputValue.value)\n }\n return\n }\n if (event.key === 'Escape') {\n dropdownOpen.value = false\n highlightedIndex.value = -1\n return\n }\n if (event.key === 'Backspace' && !inputValue.value && props.modelValue.length > 0) {\n removeTag(props.modelValue.length - 1)\n }\n}\n\nfunction handleFocus() {\n if (filteredSuggestions.value.length) dropdownOpen.value = true\n}\n\nfunction handleBlur() {\n // Delay so a click on a suggestion fires first\n setTimeout(() => {\n dropdownOpen.value = false\n }, 120)\n}\n\nfunction handlePaste(event: ClipboardEvent) {\n event.preventDefault()\n const text = event.clipboardData?.getData('text')\n if (!text) return\n\n const tags = text.split(/[,\\n]/).map(t => t.trim()).filter(Boolean)\n const newTags = [...props.modelValue]\n\n for (const tag of tags) {\n if (!canAddMore.value) break\n if (!props.allowDuplicates && newTags.includes(tag)) continue\n newTags.push(tag)\n }\n\n emit('update:modelValue', newTags)\n}\n\nfunction focusInput() {\n inputRef.value?.focus()\n}\n\nfunction setCategory(prefix: string) {\n emit('update:activeCategory', prefix === props.activeCategory ? '' : prefix)\n}\n\nfunction onInputEvent() {\n dropdownOpen.value = filteredSuggestions.value.length > 0\n highlightedIndex.value = -1\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-tags-input',\n `mld-tags-input--${size}`,\n error ? 'mld-tags-input--error' : '',\n disabled ? 'mld-tags-input--disabled' : '',\n categories.length ? 'mld-tags-input--categorized' : '',\n ]\"\n >\n <!-- Category switcher (only if categories defined) -->\n <div v-if=\"categories.length\" class=\"mld-tags-input__categories\">\n <button\n v-for=\"cat in categories\"\n :key=\"cat.prefix\"\n type=\"button\"\n :class=\"[\n 'mld-tags-input__category',\n `mld-tags-input__category--${cat.color ?? 'neutral'}`,\n activeCategory === cat.prefix ? 'mld-tags-input__category--active' : '',\n ]\"\n :disabled=\"disabled\"\n @click=\"setCategory(cat.prefix)\"\n >\n {{ cat.name }}\n </button>\n </div>\n\n <div class=\"mld-tags-input__field\" @click=\"focusInput\">\n <span\n v-for=\"(tag, index) in modelValue\"\n :key=\"`${tag}-${index}`\"\n :class=\"[\n 'mld-tags-input__tag',\n `mld-tags-input__tag--${size}`,\n categoryFor(tag) ? `mld-tags-input__tag--${categoryFor(tag)?.color ?? 'neutral'}` : '',\n ]\"\n >\n {{ tag }}\n <button\n v-if=\"!disabled\"\n type=\"button\"\n :aria-label=\"`Remove ${tag}`\"\n class=\"mld-tags-input__tag-remove\"\n @click.stop=\"removeTag(index)\"\n >\n <svg class=\"mld-tags-input__tag-remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n\n <input\n ref=\"inputRef\"\n v-model=\"inputValue\"\n type=\"text\"\n :placeholder=\"modelValue.length === 0 ? placeholder : ''\"\n :disabled=\"disabled || !canAddMore\"\n :class=\"['mld-tags-input__input', `mld-tags-input__input--${size}`]\"\n @keydown=\"handleKeydown\"\n @paste=\"handlePaste\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n @input=\"onInputEvent\"\n />\n\n <!-- Autocomplete dropdown -->\n <div\n v-if=\"dropdownOpen && filteredSuggestions.length\"\n class=\"mld-tags-input__dropdown\"\n >\n <button\n v-for=\"(sug, i) in filteredSuggestions\"\n :key=\"sug.value\"\n type=\"button\"\n :class=\"[\n 'mld-tags-input__dropdown-item',\n i === highlightedIndex ? 'mld-tags-input__dropdown-item--highlighted' : '',\n ]\"\n @mousedown.prevent=\"addTag(sug.value)\"\n >\n <span>{{ sug.value }}</span>\n <span v-if=\"sug.count !== undefined\" class=\"mld-tags-input__dropdown-count\">\n {{ sug.count }}\n </span>\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/tags-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Lets users type and add tags with autocomplete suggestions, categories, and a max-tag cap. */\nimport { ref, computed } from 'vue'\n\ninterface TagSuggestion {\n value: string\n count?: number\n}\n\ninterface TagCategory {\n name: string\n prefix: string\n color?: 'primary' | 'success' | 'warning' | 'error' | 'info' | 'neutral'\n}\n\ninterface Props {\n modelValue?: string[]\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n maxTags?: number\n allowDuplicates?: boolean\n suggestions?: string[] | TagSuggestion[]\n categories?: TagCategory[]\n activeCategory?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: () => [],\n disabled: false,\n error: false,\n size: 'md',\n allowDuplicates: false,\n suggestions: () => [],\n categories: () => [],\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string[]]\n 'update:activeCategory': [prefix: string]\n}>()\n\nconst inputValue = ref('')\nconst inputRef = ref<HTMLInputElement>()\nconst dropdownOpen = ref(false)\nconst highlightedIndex = ref(-1)\n\nconst canAddMore = computed(() => {\n if (props.maxTags === undefined) return true\n return props.modelValue.length < props.maxTags\n})\n\nconst normalizedSuggestions = computed<TagSuggestion[]>(() => {\n return props.suggestions.map(s => typeof s === 'string' ? { value: s } : s)\n})\n\nconst filteredSuggestions = computed<TagSuggestion[]>(() => {\n const q = inputValue.value.trim().toLowerCase()\n if (!q) return []\n return normalizedSuggestions.value\n .filter(s => s.value.toLowerCase().startsWith(q) && !props.modelValue.includes(s.value))\n .slice(0, 8)\n})\n\nfunction categoryFor(tag: string): TagCategory | undefined {\n const colonIdx = tag.indexOf(':')\n if (colonIdx === -1) return undefined\n const prefix = tag.slice(0, colonIdx)\n return props.categories.find(c => c.prefix === prefix)\n}\n\nfunction addTag(value: string) {\n const trimmed = value.trim()\n if (!trimmed) return\n if (!canAddMore.value) return\n\n // If a category is active and user didn't already type a prefix, prepend it\n let final = trimmed\n if (props.activeCategory && !trimmed.includes(':')) {\n final = `${props.activeCategory}:${trimmed}`\n }\n\n if (!props.allowDuplicates && props.modelValue.includes(final)) return\n\n emit('update:modelValue', [...props.modelValue, final])\n inputValue.value = ''\n dropdownOpen.value = false\n highlightedIndex.value = -1\n}\n\nfunction removeTag(index: number) {\n if (props.disabled) return\n const newTags = [...props.modelValue]\n newTags.splice(index, 1)\n emit('update:modelValue', newTags)\n}\n\nfunction handleKeydown(event: KeyboardEvent) {\n if (event.key === 'ArrowDown' && filteredSuggestions.value.length) {\n event.preventDefault()\n highlightedIndex.value = Math.min(highlightedIndex.value + 1, filteredSuggestions.value.length - 1)\n dropdownOpen.value = true\n return\n }\n if (event.key === 'ArrowUp' && filteredSuggestions.value.length) {\n event.preventDefault()\n highlightedIndex.value = Math.max(highlightedIndex.value - 1, 0)\n return\n }\n if (event.key === 'Enter' || event.key === ',') {\n event.preventDefault()\n if (highlightedIndex.value >= 0 && filteredSuggestions.value[highlightedIndex.value]) {\n addTag(filteredSuggestions.value[highlightedIndex.value].value)\n } else {\n addTag(inputValue.value)\n }\n return\n }\n if (event.key === 'Escape') {\n dropdownOpen.value = false\n highlightedIndex.value = -1\n return\n }\n if (event.key === 'Backspace' && !inputValue.value && props.modelValue.length > 0) {\n removeTag(props.modelValue.length - 1)\n }\n}\n\nfunction handleFocus() {\n if (filteredSuggestions.value.length) dropdownOpen.value = true\n}\n\nfunction handleBlur() {\n // Delay so a click on a suggestion fires first\n setTimeout(() => {\n dropdownOpen.value = false\n }, 120)\n}\n\nfunction handlePaste(event: ClipboardEvent) {\n event.preventDefault()\n const text = event.clipboardData?.getData('text')\n if (!text) return\n\n const tags = text.split(/[,\\n]/).map(t => t.trim()).filter(Boolean)\n const newTags = [...props.modelValue]\n\n for (const tag of tags) {\n if (!canAddMore.value) break\n if (!props.allowDuplicates && newTags.includes(tag)) continue\n newTags.push(tag)\n }\n\n emit('update:modelValue', newTags)\n}\n\nfunction focusInput() {\n inputRef.value?.focus()\n}\n\nfunction setCategory(prefix: string) {\n emit('update:activeCategory', prefix === props.activeCategory ? '' : prefix)\n}\n\nfunction onInputEvent() {\n dropdownOpen.value = filteredSuggestions.value.length > 0\n highlightedIndex.value = -1\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-tags-input',\n `mld-tags-input--${size}`,\n error ? 'mld-tags-input--error' : '',\n disabled ? 'mld-tags-input--disabled' : '',\n categories.length ? 'mld-tags-input--categorized' : '',\n ]\"\n >\n <!-- Category switcher (only if categories defined) -->\n <div v-if=\"categories.length\" class=\"mld-tags-input__categories\">\n <button\n v-for=\"cat in categories\"\n :key=\"cat.prefix\"\n type=\"button\"\n :class=\"[\n 'mld-tags-input__category',\n `mld-tags-input__category--${cat.color ?? 'neutral'}`,\n activeCategory === cat.prefix ? 'mld-tags-input__category--active' : '',\n ]\"\n :disabled=\"disabled\"\n @click=\"setCategory(cat.prefix)\"\n >\n {{ cat.name }}\n </button>\n </div>\n\n <div class=\"mld-tags-input__field\" @click=\"focusInput\">\n <span\n v-for=\"(tag, index) in modelValue\"\n :key=\"`${tag}-${index}`\"\n :class=\"[\n 'mld-tags-input__tag',\n `mld-tags-input__tag--${size}`,\n categoryFor(tag) ? `mld-tags-input__tag--${categoryFor(tag)?.color ?? 'neutral'}` : '',\n ]\"\n >\n {{ tag }}\n <button\n v-if=\"!disabled\"\n type=\"button\"\n :aria-label=\"`Remove ${tag}`\"\n class=\"mld-tags-input__tag-remove\"\n @click.stop=\"removeTag(index)\"\n >\n <svg class=\"mld-tags-input__tag-remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </span>\n\n <input\n ref=\"inputRef\"\n v-model=\"inputValue\"\n type=\"text\"\n :placeholder=\"modelValue.length === 0 ? placeholder : ''\"\n :disabled=\"disabled || !canAddMore\"\n :class=\"['mld-tags-input__input', `mld-tags-input__input--${size}`]\"\n @keydown=\"handleKeydown\"\n @paste=\"handlePaste\"\n @focus=\"handleFocus\"\n @blur=\"handleBlur\"\n @input=\"onInputEvent\"\n />\n\n <!-- Autocomplete dropdown -->\n <div\n v-if=\"dropdownOpen && filteredSuggestions.length\"\n class=\"mld-tags-input__dropdown\"\n >\n <button\n v-for=\"(sug, i) in filteredSuggestions\"\n :key=\"sug.value\"\n type=\"button\"\n :class=\"[\n 'mld-tags-input__dropdown-item',\n i === highlightedIndex ? 'mld-tags-input__dropdown-item--highlighted' : '',\n ]\"\n @mousedown.prevent=\"addTag(sug.value)\"\n >\n <span>{{ sug.value }}</span>\n <span v-if=\"sug.count !== undefined\" class=\"mld-tags-input__dropdown-count\">\n {{ sug.count }}\n </span>\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/tags-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Numeric input with increment/decrement buttons, optional unit label, and slider progress track. */\nimport { computed } from 'vue'\n\ninterface Props {\n modelValue?: number\n min?: number\n max?: number\n step?: number\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n placeholder?: string\n unit?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n step: 1,\n disabled: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number | undefined]\n}>()\n\nconst isSliderMode = computed(() => props.min !== undefined && props.max !== undefined)\n\nconst sliderPercent = computed(() => {\n if (!isSliderMode.value || props.modelValue === undefined) return 0\n const range = (props.max! - props.min!)\n if (range === 0) return 0\n return ((props.modelValue - props.min!) / range) * 100\n})\n\nconst canDecrement = computed(() => {\n if (props.modelValue === undefined) return true\n if (props.min === undefined) return true\n return props.modelValue > props.min\n})\n\nconst canIncrement = computed(() => {\n if (props.modelValue === undefined) return true\n if (props.max === undefined) return true\n return props.modelValue < props.max\n})\n\nfunction clamp(value: number): number {\n let result = value\n if (props.min !== undefined && result < props.min) result = props.min\n if (props.max !== undefined && result > props.max) result = props.max\n return result\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = target.value === '' ? undefined : Number(target.value)\n if (value !== undefined && !isNaN(value)) {\n emit('update:modelValue', clamp(value))\n } else {\n emit('update:modelValue', undefined)\n }\n}\n\nfunction handleSliderInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = Number(target.value)\n if (!isNaN(value)) {\n emit('update:modelValue', clamp(value))\n }\n}\n\nfunction decrement() {\n if (props.disabled || !canDecrement.value) return\n const current = props.modelValue ?? (props.max ?? 0)\n emit('update:modelValue', clamp(current - props.step))\n}\n\nfunction increment() {\n if (props.disabled || !canIncrement.value) return\n const current = props.modelValue ?? (props.min ?? 0)\n emit('update:modelValue', clamp(current + props.step))\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-number-input',\n `mld-number-input--${size}`,\n isSliderMode ? 'mld-number-input--slider' : 'mld-number-input--stepper',\n error ? 'mld-number-input--error' : '',\n disabled ? 'mld-number-input--disabled' : '',\n ]\"\n >\n <div class=\"mld-number-input__field\">\n <input\n type=\"number\"\n :value=\"modelValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n :class=\"[\n 'mld-number-input__input',\n `mld-number-input__input--${size}`,\n ]\"\n @input=\"handleInput\"\n />\n\n <span v-if=\"unit\" class=\"mld-number-input__unit\">{{ unit }}</span>\n\n <div class=\"mld-number-input__steppers\" aria-hidden=\"true\">\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-label=\"Increase value\"\n :disabled=\"disabled || !canIncrement\"\n class=\"mld-number-input__stepper mld-number-input__stepper--up\"\n @click=\"increment\"\n >\n <svg viewBox=\"0 0 10 6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 5l4-4 4 4\" />\n </svg>\n </button>\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-label=\"Decrease value\"\n :disabled=\"disabled || !canDecrement\"\n class=\"mld-number-input__stepper mld-number-input__stepper--down\"\n @click=\"decrement\"\n >\n <svg viewBox=\"0 0 10 6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 1l4 4 4-4\" />\n </svg>\n </button>\n </div>\n </div>\n\n <input\n v-if=\"isSliderMode\"\n type=\"range\"\n :value=\"modelValue ?? min\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :style=\"{ '--mint-slider-fill': `${sliderPercent}%` }\"\n class=\"mld-number-input__slider\"\n @input=\"handleSliderInput\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/number-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Numeric input with increment/decrement buttons, optional unit label, and slider progress track. */\nimport { computed } from 'vue'\n\ninterface Props {\n modelValue?: number\n min?: number\n max?: number\n step?: number\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n placeholder?: string\n unit?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n step: 1,\n disabled: false,\n error: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number | undefined]\n}>()\n\nconst isSliderMode = computed(() => props.min !== undefined && props.max !== undefined)\n\nconst sliderPercent = computed(() => {\n if (!isSliderMode.value || props.modelValue === undefined) return 0\n const range = (props.max! - props.min!)\n if (range === 0) return 0\n return ((props.modelValue - props.min!) / range) * 100\n})\n\nconst canDecrement = computed(() => {\n if (props.modelValue === undefined) return true\n if (props.min === undefined) return true\n return props.modelValue > props.min\n})\n\nconst canIncrement = computed(() => {\n if (props.modelValue === undefined) return true\n if (props.max === undefined) return true\n return props.modelValue < props.max\n})\n\nfunction clamp(value: number): number {\n let result = value\n if (props.min !== undefined && result < props.min) result = props.min\n if (props.max !== undefined && result > props.max) result = props.max\n return result\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = target.value === '' ? undefined : Number(target.value)\n if (value !== undefined && !isNaN(value)) {\n emit('update:modelValue', clamp(value))\n } else {\n emit('update:modelValue', undefined)\n }\n}\n\nfunction handleSliderInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = Number(target.value)\n if (!isNaN(value)) {\n emit('update:modelValue', clamp(value))\n }\n}\n\nfunction decrement() {\n if (props.disabled || !canDecrement.value) return\n const current = props.modelValue ?? (props.max ?? 0)\n emit('update:modelValue', clamp(current - props.step))\n}\n\nfunction increment() {\n if (props.disabled || !canIncrement.value) return\n const current = props.modelValue ?? (props.min ?? 0)\n emit('update:modelValue', clamp(current + props.step))\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-number-input',\n `mld-number-input--${size}`,\n isSliderMode ? 'mld-number-input--slider' : 'mld-number-input--stepper',\n error ? 'mld-number-input--error' : '',\n disabled ? 'mld-number-input--disabled' : '',\n ]\"\n >\n <div class=\"mld-number-input__field\">\n <input\n type=\"number\"\n :value=\"modelValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n :class=\"[\n 'mld-number-input__input',\n `mld-number-input__input--${size}`,\n ]\"\n @input=\"handleInput\"\n />\n\n <span v-if=\"unit\" class=\"mld-number-input__unit\">{{ unit }}</span>\n\n <div class=\"mld-number-input__steppers\" aria-hidden=\"true\">\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-label=\"Increase value\"\n :disabled=\"disabled || !canIncrement\"\n class=\"mld-number-input__stepper mld-number-input__stepper--up\"\n @click=\"increment\"\n >\n <svg viewBox=\"0 0 10 6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 5l4-4 4 4\" />\n </svg>\n </button>\n <button\n type=\"button\"\n tabindex=\"-1\"\n aria-label=\"Decrease value\"\n :disabled=\"disabled || !canDecrement\"\n class=\"mld-number-input__stepper mld-number-input__stepper--down\"\n @click=\"decrement\"\n >\n <svg viewBox=\"0 0 10 6\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M1 1l4 4 4-4\" />\n </svg>\n </button>\n </div>\n </div>\n\n <input\n v-if=\"isSliderMode\"\n type=\"range\"\n :value=\"modelValue ?? min\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :style=\"{ '--mint-slider-fill': `${sliderPercent}%` }\"\n class=\"mld-number-input__slider\"\n @input=\"handleSliderInput\"\n />\n </div>\n</template>\n\n<style>\n@import '../styles/components/number-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Drag-and-drop file upload zone with type filtering, size validation, and folder mode. */\nimport { ref, computed } from 'vue'\n\ninterface Props {\n accept?: string\n multiple?: boolean\n maxSize?: number\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n mode?: 'file' | 'folder'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n multiple: false,\n disabled: false,\n size: 'md',\n mode: 'file',\n})\n\nconst emit = defineEmits<{\n upload: [files: File[]]\n error: [message: string]\n}>()\n\nconst isDragOver = ref(false)\nconst inputRef = ref<HTMLInputElement>()\nconst selectedFiles = ref<File[]>([])\nconst folderName = ref<string | null>(null)\n\nconst isFolder = computed(() => props.mode === 'folder')\n\nconst acceptLabel = computed(() => {\n if (!props.accept) return 'any file'\n return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')\n})\n\nconst maxSizeLabel = computed(() => {\n if (!props.maxSize) return null\n if (props.maxSize >= 1024 * 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024 * 1024)).toFixed(1)} GB`\n }\n if (props.maxSize >= 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024)).toFixed(1)} MB`\n }\n if (props.maxSize >= 1024) {\n return `${(props.maxSize / 1024).toFixed(1)} KB`\n }\n return `${props.maxSize} bytes`\n})\n\nconst fileTypeMap: Record<string, { color: string; label: string }> = {\n csv: { color: '#10B981', label: 'CSV' },\n tsv: { color: '#10B981', label: 'TSV' },\n xlsx: { color: '#10B981', label: 'XLSX' },\n xls: { color: '#10B981', label: 'XLS' },\n pdf: { color: '#EF4444', label: 'PDF' },\n png: { color: '#8B5CF6', label: 'PNG' },\n jpg: { color: '#8B5CF6', label: 'JPG' },\n jpeg: { color: '#8B5CF6', label: 'JPEG' },\n gif: { color: '#8B5CF6', label: 'GIF' },\n svg: { color: '#8B5CF6', label: 'SVG' },\n webp: { color: '#8B5CF6', label: 'WEBP' },\n json: { color: '#F59E0B', label: 'JSON' },\n xml: { color: '#F59E0B', label: 'XML' },\n txt: { color: '#64748B', label: 'TXT' },\n zip: { color: '#6366F1', label: 'ZIP' },\n gz: { color: '#6366F1', label: 'GZ' },\n py: { color: '#3B82F6', label: 'PY' },\n js: { color: '#EAB308', label: 'JS' },\n ts: { color: '#3B82F6', label: 'TS' },\n fasta: { color: '#EC4899', label: 'FASTA' },\n fastq: { color: '#EC4899', label: 'FASTQ' },\n bam: { color: '#EC4899', label: 'BAM' },\n vcf: { color: '#EC4899', label: 'VCF' },\n}\n\nfunction getFileType(filename: string): { color: string; label: string } {\n const ext = filename.split('.').pop()?.toLowerCase() || ''\n return fileTypeMap[ext] || { color: '#94A3B8', label: ext.toUpperCase() || 'FILE' }\n}\n\nfunction validateFiles(files: FileList | File[]): File[] {\n const validFiles: File[] = []\n\n for (const file of Array.from(files)) {\n if (props.maxSize && file.size > props.maxSize) {\n emit('error', `File \"${file.name}\" exceeds maximum size of ${maxSizeLabel.value}`)\n continue\n }\n\n if (props.accept) {\n const acceptedTypes = props.accept.split(',').map(t => t.trim().toLowerCase())\n const fileExt = '.' + file.name.split('.').pop()?.toLowerCase()\n const fileType = file.type.toLowerCase()\n\n const isAccepted = acceptedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type\n }\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'))\n }\n return fileType === type\n })\n\n if (!isAccepted) {\n emit('error', `File \"${file.name}\" is not an accepted file type`)\n continue\n }\n }\n\n validFiles.push(file)\n }\n\n return validFiles\n}\n\nfunction handleFiles(files: FileList | File[], folder?: string) {\n const validFiles = validateFiles(files)\n if (validFiles.length === 0) return\n\n if (isFolder.value) {\n folderName.value = folder || extractFolderName(validFiles)\n selectedFiles.value = validFiles\n emit('upload', validFiles)\n } else if (!props.multiple) {\n selectedFiles.value = [validFiles[0]]\n emit('upload', [validFiles[0]])\n } else {\n selectedFiles.value = [...selectedFiles.value, ...validFiles]\n emit('upload', validFiles)\n }\n}\n\nfunction extractFolderName(files: File[]): string | null {\n if (files.length === 0) return null\n const firstPath = files[0].webkitRelativePath\n if (firstPath) {\n return firstPath.split('/')[0]\n }\n return null\n}\n\nfunction handleDrop(event: DragEvent) {\n event.preventDefault()\n isDragOver.value = false\n if (props.disabled) return\n\n if (isFolder.value && event.dataTransfer?.items) {\n handleFolderDrop(event.dataTransfer.items)\n } else if (event.dataTransfer?.files) {\n handleFiles(event.dataTransfer.files)\n }\n}\n\nasync function handleFolderDrop(items: DataTransferItemList) {\n const files: File[] = []\n let folderNameFromDrop: string | undefined\n\n for (const item of Array.from(items)) {\n const entry = item.webkitGetAsEntry?.()\n if (entry?.isDirectory) {\n folderNameFromDrop = entry.name\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n } else if (entry?.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n }\n }\n\n if (files.length > 0) {\n handleFiles(files, folderNameFromDrop)\n }\n}\n\nasync function readDirectory(directory: FileSystemDirectoryEntry, files: File[]): Promise<void> {\n const reader = directory.createReader()\n // readEntries returns at most 100 entries per call per spec — must loop\n let batch: FileSystemEntry[]\n do {\n batch = await new Promise<FileSystemEntry[]>((resolve, reject) => {\n reader.readEntries(resolve, reject)\n })\n for (const entry of batch) {\n if (entry.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n } else if (entry.isDirectory) {\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n }\n }\n } while (batch.length > 0)\n}\n\nfunction getFile(entry: FileSystemFileEntry): Promise<File | null> {\n return new Promise((resolve) => {\n entry.file(resolve, () => resolve(null))\n })\n}\n\nfunction handleDragOver(event: DragEvent) {\n event.preventDefault()\n if (!props.disabled) {\n isDragOver.value = true\n }\n}\n\nfunction handleDragLeave() {\n isDragOver.value = false\n}\n\nfunction handleInputChange(event: Event) {\n const target = event.target as HTMLInputElement\n if (target.files) {\n handleFiles(target.files)\n }\n target.value = ''\n}\n\nfunction openFilePicker() {\n if (!props.disabled) {\n inputRef.value?.click()\n }\n}\n\nfunction removeFile(index: number) {\n selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index)\n if (isFolder.value && selectedFiles.value.length === 0) {\n folderName.value = null\n }\n}\n\nfunction clearAll() {\n selectedFiles.value = []\n folderName.value = null\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`\n }\n return `${bytes} bytes`\n}\n</script>\n\n<template>\n <div class=\"mld-file-uploader\">\n <!-- Dropzone -->\n <div\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"disabled ? 'File upload disabled' : 'Click or drag files to upload'\"\n :aria-disabled=\"disabled\"\n :class=\"[\n 'mld-file-uploader__dropzone',\n `mld-file-uploader__dropzone--${size}`,\n isDragOver ? 'mld-file-uploader__dropzone--dragover' : '',\n disabled ? 'mld-file-uploader__dropzone--disabled' : '',\n selectedFiles.length > 0 ? 'mld-file-uploader__dropzone--has-files' : '',\n ]\"\n @click=\"openFilePicker\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n @drop=\"handleDrop\"\n @dragover=\"handleDragOver\"\n @dragleave=\"handleDragLeave\"\n >\n <input\n ref=\"inputRef\"\n type=\"file\"\n :accept=\"accept\"\n :multiple=\"isFolder || multiple\"\n :disabled=\"disabled\"\n :webkitdirectory=\"isFolder || undefined\"\n class=\"mld-file-uploader__input\"\n @change=\"handleInputChange\"\n />\n\n <div class=\"mld-file-uploader__content\">\n <svg\n v-if=\"isFolder\"\n class=\"mld-file-uploader__icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\" />\n </svg>\n <svg\n v-else\n class=\"mld-file-uploader__icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3\" /><path d=\"m16 16-4-4-4 4\" /><path d=\"M12 12v9\" />\n </svg>\n\n <p class=\"mld-file-uploader__text\">\n Drop {{ acceptLabel }} or click\n </p>\n </div>\n </div>\n\n <!-- Actions slot (e.g. template buttons) -->\n <div v-if=\"$slots.actions\" class=\"mld-file-uploader__actions\">\n <slot name=\"actions\" />\n </div>\n\n <!-- Folder summary display -->\n <div v-if=\"isFolder && folderName && selectedFiles.length > 0\" class=\"mld-file-uploader__folder-summary\">\n <div class=\"mld-file-uploader__folder-info\">\n <div class=\"mld-file-uploader__folder-icon-wrapper\">\n <svg class=\"mld-file-uploader__folder-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\" />\n </svg>\n </div>\n <div class=\"mld-file-uploader__folder-details\">\n <span class=\"mld-file-uploader__folder-name\">{{ folderName }}</span>\n <span class=\"mld-file-uploader__folder-count\">{{ selectedFiles.length }} file{{ selectedFiles.length !== 1 ? 's' : '' }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear folder\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"clearAll\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- File list display -->\n <TransitionGroup\n v-if=\"!isFolder && selectedFiles.length > 0\"\n tag=\"ul\"\n name=\"mld-file-uploader-item\"\n class=\"mld-file-uploader__list\"\n >\n <li\n v-for=\"(file, index) in selectedFiles\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"mld-file-uploader__file\"\n >\n <div class=\"mld-file-uploader__file-info\">\n <span\n class=\"mld-file-uploader__file-badge\"\n :style=\"{ '--badge-color': getFileType(file.name).color }\"\n >\n {{ getFileType(file.name).label }}\n </span>\n <div class=\"mld-file-uploader__file-meta\">\n <span class=\"mld-file-uploader__file-name\">{{ file.name }}</span>\n <span class=\"mld-file-uploader__file-size\">{{ formatFileSize(file.size) }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n :aria-label=\"`Remove ${file.name}`\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"removeFile(index)\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </li>\n </TransitionGroup>\n </div>\n</template>\n\n<style>\n@import '../styles/components/file-uploader.css';\n</style>\n","<script setup lang=\"ts\">\n/** Drag-and-drop file upload zone with type filtering, size validation, and folder mode. */\nimport { ref, computed } from 'vue'\n\ninterface Props {\n accept?: string\n multiple?: boolean\n maxSize?: number\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n mode?: 'file' | 'folder'\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n multiple: false,\n disabled: false,\n size: 'md',\n mode: 'file',\n})\n\nconst emit = defineEmits<{\n upload: [files: File[]]\n error: [message: string]\n}>()\n\nconst isDragOver = ref(false)\nconst inputRef = ref<HTMLInputElement>()\nconst selectedFiles = ref<File[]>([])\nconst folderName = ref<string | null>(null)\n\nconst isFolder = computed(() => props.mode === 'folder')\n\nconst acceptLabel = computed(() => {\n if (!props.accept) return 'any file'\n return props.accept.split(',').map(a => a.trim().replace('.', '').toUpperCase()).join(', ')\n})\n\nconst maxSizeLabel = computed(() => {\n if (!props.maxSize) return null\n if (props.maxSize >= 1024 * 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024 * 1024)).toFixed(1)} GB`\n }\n if (props.maxSize >= 1024 * 1024) {\n return `${(props.maxSize / (1024 * 1024)).toFixed(1)} MB`\n }\n if (props.maxSize >= 1024) {\n return `${(props.maxSize / 1024).toFixed(1)} KB`\n }\n return `${props.maxSize} bytes`\n})\n\nconst fileTypeMap: Record<string, { color: string; label: string }> = {\n csv: { color: '#10B981', label: 'CSV' },\n tsv: { color: '#10B981', label: 'TSV' },\n xlsx: { color: '#10B981', label: 'XLSX' },\n xls: { color: '#10B981', label: 'XLS' },\n pdf: { color: '#EF4444', label: 'PDF' },\n png: { color: '#8B5CF6', label: 'PNG' },\n jpg: { color: '#8B5CF6', label: 'JPG' },\n jpeg: { color: '#8B5CF6', label: 'JPEG' },\n gif: { color: '#8B5CF6', label: 'GIF' },\n svg: { color: '#8B5CF6', label: 'SVG' },\n webp: { color: '#8B5CF6', label: 'WEBP' },\n json: { color: '#F59E0B', label: 'JSON' },\n xml: { color: '#F59E0B', label: 'XML' },\n txt: { color: '#64748B', label: 'TXT' },\n zip: { color: '#6366F1', label: 'ZIP' },\n gz: { color: '#6366F1', label: 'GZ' },\n py: { color: '#3B82F6', label: 'PY' },\n js: { color: '#EAB308', label: 'JS' },\n ts: { color: '#3B82F6', label: 'TS' },\n fasta: { color: '#EC4899', label: 'FASTA' },\n fastq: { color: '#EC4899', label: 'FASTQ' },\n bam: { color: '#EC4899', label: 'BAM' },\n vcf: { color: '#EC4899', label: 'VCF' },\n}\n\nfunction getFileType(filename: string): { color: string; label: string } {\n const ext = filename.split('.').pop()?.toLowerCase() || ''\n return fileTypeMap[ext] || { color: '#94A3B8', label: ext.toUpperCase() || 'FILE' }\n}\n\nfunction validateFiles(files: FileList | File[]): File[] {\n const validFiles: File[] = []\n\n for (const file of Array.from(files)) {\n if (props.maxSize && file.size > props.maxSize) {\n emit('error', `File \"${file.name}\" exceeds maximum size of ${maxSizeLabel.value}`)\n continue\n }\n\n if (props.accept) {\n const acceptedTypes = props.accept.split(',').map(t => t.trim().toLowerCase())\n const fileExt = '.' + file.name.split('.').pop()?.toLowerCase()\n const fileType = file.type.toLowerCase()\n\n const isAccepted = acceptedTypes.some(type => {\n if (type.startsWith('.')) {\n return fileExt === type\n }\n if (type.endsWith('/*')) {\n return fileType.startsWith(type.replace('/*', '/'))\n }\n return fileType === type\n })\n\n if (!isAccepted) {\n emit('error', `File \"${file.name}\" is not an accepted file type`)\n continue\n }\n }\n\n validFiles.push(file)\n }\n\n return validFiles\n}\n\nfunction handleFiles(files: FileList | File[], folder?: string) {\n const validFiles = validateFiles(files)\n if (validFiles.length === 0) return\n\n if (isFolder.value) {\n folderName.value = folder || extractFolderName(validFiles)\n selectedFiles.value = validFiles\n emit('upload', validFiles)\n } else if (!props.multiple) {\n selectedFiles.value = [validFiles[0]]\n emit('upload', [validFiles[0]])\n } else {\n selectedFiles.value = [...selectedFiles.value, ...validFiles]\n emit('upload', validFiles)\n }\n}\n\nfunction extractFolderName(files: File[]): string | null {\n if (files.length === 0) return null\n const firstPath = files[0].webkitRelativePath\n if (firstPath) {\n return firstPath.split('/')[0]\n }\n return null\n}\n\nfunction handleDrop(event: DragEvent) {\n event.preventDefault()\n isDragOver.value = false\n if (props.disabled) return\n\n if (isFolder.value && event.dataTransfer?.items) {\n handleFolderDrop(event.dataTransfer.items)\n } else if (event.dataTransfer?.files) {\n handleFiles(event.dataTransfer.files)\n }\n}\n\nasync function handleFolderDrop(items: DataTransferItemList) {\n const files: File[] = []\n let folderNameFromDrop: string | undefined\n\n for (const item of Array.from(items)) {\n const entry = item.webkitGetAsEntry?.()\n if (entry?.isDirectory) {\n folderNameFromDrop = entry.name\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n } else if (entry?.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n }\n }\n\n if (files.length > 0) {\n handleFiles(files, folderNameFromDrop)\n }\n}\n\nasync function readDirectory(directory: FileSystemDirectoryEntry, files: File[]): Promise<void> {\n const reader = directory.createReader()\n // readEntries returns at most 100 entries per call per spec — must loop\n let batch: FileSystemEntry[]\n do {\n batch = await new Promise<FileSystemEntry[]>((resolve, reject) => {\n reader.readEntries(resolve, reject)\n })\n for (const entry of batch) {\n if (entry.isFile) {\n const file = await getFile(entry as FileSystemFileEntry)\n if (file) files.push(file)\n } else if (entry.isDirectory) {\n await readDirectory(entry as FileSystemDirectoryEntry, files)\n }\n }\n } while (batch.length > 0)\n}\n\nfunction getFile(entry: FileSystemFileEntry): Promise<File | null> {\n return new Promise((resolve) => {\n entry.file(resolve, () => resolve(null))\n })\n}\n\nfunction handleDragOver(event: DragEvent) {\n event.preventDefault()\n if (!props.disabled) {\n isDragOver.value = true\n }\n}\n\nfunction handleDragLeave() {\n isDragOver.value = false\n}\n\nfunction handleInputChange(event: Event) {\n const target = event.target as HTMLInputElement\n if (target.files) {\n handleFiles(target.files)\n }\n target.value = ''\n}\n\nfunction openFilePicker() {\n if (!props.disabled) {\n inputRef.value?.click()\n }\n}\n\nfunction removeFile(index: number) {\n selectedFiles.value = selectedFiles.value.filter((_, i) => i !== index)\n if (isFolder.value && selectedFiles.value.length === 0) {\n folderName.value = null\n }\n}\n\nfunction clearAll() {\n selectedFiles.value = []\n folderName.value = null\n}\n\nfunction formatFileSize(bytes: number): string {\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`\n }\n return `${bytes} bytes`\n}\n</script>\n\n<template>\n <div class=\"mld-file-uploader\">\n <!-- Dropzone -->\n <div\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"disabled ? 'File upload disabled' : 'Click or drag files to upload'\"\n :aria-disabled=\"disabled\"\n :class=\"[\n 'mld-file-uploader__dropzone',\n `mld-file-uploader__dropzone--${size}`,\n isDragOver ? 'mld-file-uploader__dropzone--dragover' : '',\n disabled ? 'mld-file-uploader__dropzone--disabled' : '',\n selectedFiles.length > 0 ? 'mld-file-uploader__dropzone--has-files' : '',\n ]\"\n @click=\"openFilePicker\"\n @keydown.enter=\"openFilePicker\"\n @keydown.space.prevent=\"openFilePicker\"\n @drop=\"handleDrop\"\n @dragover=\"handleDragOver\"\n @dragleave=\"handleDragLeave\"\n >\n <input\n ref=\"inputRef\"\n type=\"file\"\n :accept=\"accept\"\n :multiple=\"isFolder || multiple\"\n :disabled=\"disabled\"\n :webkitdirectory=\"isFolder || undefined\"\n class=\"mld-file-uploader__input\"\n @change=\"handleInputChange\"\n />\n\n <div class=\"mld-file-uploader__content\">\n <svg\n v-if=\"isFolder\"\n class=\"mld-file-uploader__icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\" />\n </svg>\n <svg\n v-else\n class=\"mld-file-uploader__icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"1.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n viewBox=\"0 0 24 24\"\n >\n <path d=\"M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3\" /><path d=\"m16 16-4-4-4 4\" /><path d=\"M12 12v9\" />\n </svg>\n\n <p class=\"mld-file-uploader__text\">\n Drop {{ acceptLabel }} or click\n </p>\n </div>\n </div>\n\n <!-- Actions slot (e.g. template buttons) -->\n <div v-if=\"$slots.actions\" class=\"mld-file-uploader__actions\">\n <slot name=\"actions\" />\n </div>\n\n <!-- Folder summary display -->\n <div v-if=\"isFolder && folderName && selectedFiles.length > 0\" class=\"mld-file-uploader__folder-summary\">\n <div class=\"mld-file-uploader__folder-info\">\n <div class=\"mld-file-uploader__folder-icon-wrapper\">\n <svg class=\"mld-file-uploader__folder-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"m6 14 1.5-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.54 6a2 2 0 0 1-1.95 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.9a2 2 0 0 1 1.69.9l.81 1.2a2 2 0 0 0 1.67.9H18a2 2 0 0 1 2 2v2\" />\n </svg>\n </div>\n <div class=\"mld-file-uploader__folder-details\">\n <span class=\"mld-file-uploader__folder-name\">{{ folderName }}</span>\n <span class=\"mld-file-uploader__folder-count\">{{ selectedFiles.length }} file{{ selectedFiles.length !== 1 ? 's' : '' }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n aria-label=\"Clear folder\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"clearAll\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </div>\n\n <!-- File list display -->\n <TransitionGroup\n v-if=\"!isFolder && selectedFiles.length > 0\"\n tag=\"ul\"\n name=\"mld-file-uploader-item\"\n class=\"mld-file-uploader__list\"\n >\n <li\n v-for=\"(file, index) in selectedFiles\"\n :key=\"`${file.name}-${file.size}-${index}`\"\n class=\"mld-file-uploader__file\"\n >\n <div class=\"mld-file-uploader__file-info\">\n <span\n class=\"mld-file-uploader__file-badge\"\n :style=\"{ '--badge-color': getFileType(file.name).color }\"\n >\n {{ getFileType(file.name).label }}\n </span>\n <div class=\"mld-file-uploader__file-meta\">\n <span class=\"mld-file-uploader__file-name\">{{ file.name }}</span>\n <span class=\"mld-file-uploader__file-size\">{{ formatFileSize(file.size) }}</span>\n </div>\n </div>\n <button\n type=\"button\"\n :aria-label=\"`Remove ${file.name}`\"\n class=\"mld-file-uploader__remove-btn\"\n @click.stop=\"removeFile(index)\"\n >\n <svg class=\"mld-file-uploader__remove-icon\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" viewBox=\"0 0 24 24\">\n <path d=\"M18 6 6 18\" /><path d=\"m6 6 12 12\" />\n </svg>\n </button>\n </li>\n </TransitionGroup>\n </div>\n</template>\n\n<style>\n@import '../styles/components/file-uploader.css';\n</style>\n","import { ref } from 'vue'\nimport type { Toast } from '../types'\n\nconst toasts = ref<Toast[]>([])\nlet nextId = 0\n\n/** Reactive global toast queue with success/error/warning/info variants and auto-dismiss timers. */\nexport function useToast() {\n function show(message: string, type: Toast['type'] = 'success', duration = 3500) {\n const id = nextId++\n toasts.value.push({ id, message, type, duration })\n setTimeout(() => dismiss(id), duration)\n }\n\n function success(message: string, duration = 3500) {\n show(message, 'success', duration)\n }\n\n function error(message: string, duration = 5000) {\n show(message, 'error', duration)\n }\n\n function warning(message: string, duration = 4000) {\n show(message, 'warning', duration)\n }\n\n function info(message: string, duration = 3500) {\n show(message, 'info', duration)\n }\n\n function dismiss(id: number) {\n toasts.value = toasts.value.filter(t => t.id !== id)\n }\n\n function clear() {\n toasts.value = []\n }\n\n return { toasts, show, success, error, warning, info, dismiss, clear }\n}\n","import { computed, onUnmounted, getCurrentInstance, type Ref } from 'vue'\nimport { useSettingsStore } from '../stores/settings'\n\nexport interface UseThemeReturn {\n isDark: Ref<boolean>\n toggleTheme: () => void\n setTheme: (theme: 'light' | 'dark') => void\n}\n\n/** Reads and toggles the active theme (light/dark/system) with reactivity to OS preference changes. */\nexport function useTheme(): UseThemeReturn {\n const settings = useSettingsStore()\n\n // Load persisted theme from localStorage (idempotent — safe to call multiple times)\n settings.initialize()\n\n const mql = typeof window !== 'undefined'\n ? window.matchMedia('(prefers-color-scheme: dark)')\n : null\n\n const isDark = computed(() => {\n if (settings.theme === 'system') {\n return mql?.matches ?? false\n }\n return settings.theme === 'dark'\n })\n\n // React to system preference changes when in 'system' mode\n function onSystemChange() {\n if (settings.theme === 'system') {\n // Force reactivity by toggling theme to itself — the computed re-evaluates\n // because mql.matches changed, but Vue needs a trigger. We nudge the store.\n settings.theme = 'system'\n }\n }\n\n mql?.addEventListener('change', onSystemChange)\n\n if (getCurrentInstance()) {\n onUnmounted(() => {\n mql?.removeEventListener('change', onSystemChange)\n })\n }\n\n function toggleTheme() {\n settings.theme = isDark.value ? 'light' : 'dark'\n }\n\n function setTheme(theme: 'light' | 'dark') {\n settings.theme = theme\n }\n\n return {\n isDark,\n toggleTheme,\n setTheme,\n }\n}\n","import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'\nimport { useSettingsStore } from '../stores/settings'\nimport { useAuthStore } from '../stores/auth'\n\nlet apiClientInstance: AxiosInstance | null = null\nlet interceptorAttached = false\n\nfunction getApiClient(): AxiosInstance {\n if (!apiClientInstance) {\n apiClientInstance = axios.create({\n headers: {\n 'Content-Type': 'application/json',\n },\n })\n }\n return apiClientInstance\n}\n\nexport interface ApiClientOptions {\n baseUrl?: string\n timeout?: number\n withAuth?: boolean\n}\n\nexport interface UseApiReturn {\n client: AxiosInstance\n get: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n post: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n put: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n patch: <T>(url: string, data?: unknown, config?: AxiosRequestConfig) => Promise<T>\n delete: <T>(url: string, config?: AxiosRequestConfig) => Promise<T>\n upload: <T>(url: string, file: File, fieldName?: string, additionalData?: Record<string, unknown>) => Promise<T>\n download: (url: string, filename?: string) => Promise<string>\n buildUrl: (path: string) => string\n buildWsUrl: (path: string) => string\n}\n\n/** Axios-based API client that injects the plugin base URL and JWT auth header on every request. */\nexport function useApi(options: ApiClientOptions = {}): UseApiReturn {\n const settingsStore = useSettingsStore()\n const authStore = useAuthStore()\n const apiClient = getApiClient()\n\n // Ensure auth store is initialized (reads token from localStorage)\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n\n // Attach auth interceptor only once (reads token dynamically, not from closure)\n if (!interceptorAttached) {\n apiClient.interceptors.request.use((config) => {\n if (authStore.token && config.headers && !config.headers.Authorization) {\n config.headers.Authorization = `Bearer ${authStore.token}`\n }\n return config\n })\n interceptorAttached = true\n }\n\n // Build per-request config that applies this caller's options\n function requestConfig(config?: AxiosRequestConfig): AxiosRequestConfig {\n const base: AxiosRequestConfig = {\n baseURL: options.baseUrl ?? settingsStore.getApiBaseUrl(),\n timeout: options.timeout ?? settingsStore.requestTimeout,\n ...config,\n }\n // Strip auth header if explicitly disabled\n if (options.withAuth === false) {\n base.headers = { ...base.headers, Authorization: undefined }\n }\n return base\n }\n\n // Generic request methods\n async function get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.get<T>(url, requestConfig(config))\n return response.data\n }\n\n async function post<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.post<T>(url, data, requestConfig(config))\n return response.data\n }\n\n async function put<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.put<T>(url, data, requestConfig(config))\n return response.data\n }\n\n async function patch<T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.patch<T>(url, data, requestConfig(config))\n return response.data\n }\n\n async function del<T>(url: string, config?: AxiosRequestConfig): Promise<T> {\n const response = await apiClient.delete<T>(url, requestConfig(config))\n return response.data\n }\n\n // File upload helper\n async function upload<T>(url: string, file: File, fieldName = 'file', additionalData?: Record<string, unknown>): Promise<T> {\n const formData = new FormData()\n formData.append(fieldName, file)\n\n if (additionalData) {\n Object.entries(additionalData).forEach(([key, value]) => {\n if (value !== undefined && value !== null) {\n formData.append(key, typeof value === 'object' ? JSON.stringify(value) : String(value))\n }\n })\n }\n\n const response = await apiClient.post<T>(url, formData, requestConfig({\n // Let Axios set Content-Type with the correct multipart boundary\n headers: { 'Content-Type': undefined },\n }))\n return response.data\n }\n\n // Download helper - returns blob URL\n async function download(url: string, filename?: string): Promise<string> {\n const response = await apiClient.get(url, requestConfig({ responseType: 'blob' }))\n const blob = new Blob([response.data])\n const blobUrl = URL.createObjectURL(blob)\n\n // Optionally trigger download\n if (filename) {\n const link = document.createElement('a')\n link.href = blobUrl\n link.download = filename\n document.body.appendChild(link)\n link.click()\n document.body.removeChild(link)\n // Revoke after a short delay to allow download to start;\n // return empty string since the URL will be invalidated\n setTimeout(() => URL.revokeObjectURL(blobUrl), 100)\n return ''\n }\n\n // Caller is responsible for revoking when no filename is provided\n return blobUrl\n }\n\n // Build full URL for external use (e.g., <a href=\"...\">)\n function buildUrl(path: string): string {\n const baseUrl = options.baseUrl ?? settingsStore.getApiBaseUrl()\n return `${baseUrl}${path}`\n }\n\n // WebSocket URL builder\n function buildWsUrl(path: string): string {\n return `${settingsStore.getWsBaseUrl()}${path}`\n }\n\n return {\n client: apiClient,\n get,\n post,\n put,\n patch,\n delete: del,\n upload,\n download,\n buildUrl,\n buildWsUrl,\n }\n}\n","import type { DatePreset, ExperimentStatus, SelectOption, PillVariant } from '../types'\n\nexport function formatExperimentDate(dateStr: string): string {\n try {\n return new Date(dateStr).toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n })\n } catch {\n return dateStr\n }\n}\n\nexport function datePresetToISO(preset: DatePreset): string {\n const now = new Date()\n const days = preset === 'last_7_days' ? 7 : preset === 'last_30_days' ? 30 : 90\n const d = new Date(now.getTime() - days * 86_400_000)\n return d.toISOString()\n}\n\nexport const EXPERIMENT_STATUS_OPTIONS: SelectOption<string>[] = [\n { value: '', label: 'All statuses' },\n { value: 'planned', label: 'Planned' },\n { value: 'ongoing', label: 'Ongoing' },\n { value: 'completed', label: 'Completed' },\n { value: 'cancelled', label: 'Cancelled' },\n]\n\nexport const EXPERIMENT_STATUS_VARIANT_MAP: Record<ExperimentStatus, PillVariant> = {\n planned: 'default',\n ongoing: 'primary',\n completed: 'success',\n cancelled: 'error',\n}\n\nexport const EXPERIMENT_STATUS_LABELS: Record<ExperimentStatus, string> = {\n planned: 'Planned',\n ongoing: 'Ongoing',\n completed: 'Completed',\n cancelled: 'Cancelled',\n}\n\nexport const DATE_PRESET_OPTIONS: SelectOption<string>[] = [\n { value: '', label: 'Any time' },\n { value: 'last_7_days', label: 'Last 7 days' },\n { value: 'last_30_days', label: 'Last 30 days' },\n { value: 'last_90_days', label: 'Last 90 days' },\n]\n\nexport const SORT_OPTIONS: SelectOption<string>[] = [\n { value: 'created_at:desc', label: 'Newest first' },\n { value: 'created_at:asc', label: 'Oldest first' },\n { value: 'updated_at:desc', label: 'Recently updated' },\n { value: 'name:asc', label: 'Name A\\u2013Z' },\n { value: 'name:desc', label: 'Name Z\\u2013A' },\n]\n","import { ref, reactive, computed, watch, onScopeDispose, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport { datePresetToISO } from './experiment-utils'\nimport type {\n ExperimentSummary,\n ExperimentListResponse,\n ExperimentFilters,\n ExperimentTypeOption,\n ExperimentSortField,\n PlatformContext,\n SelectOption,\n} from '../types'\n\nfunction getPlatformContext(): PlatformContext | undefined {\n if (typeof window === 'undefined') return undefined\n return (window as unknown as { __MINT_PLATFORM__?: PlatformContext }).__MINT_PLATFORM__\n}\n\nfunction getPlatformApiUrl(): string | undefined {\n return getPlatformContext()?.platformApiUrl\n}\n\nexport interface UseExperimentSelectorOptions {\n experimentType?: string\n apiBaseUrl?: string\n limit?: number\n immediate?: boolean\n}\n\nexport interface UseExperimentSelectorReturn {\n experiments: Ref<ExperimentSummary[]>\n total: Ref<number>\n selectedExperiment: Ref<ExperimentSummary | null>\n filters: ExperimentFilters\n isLoading: Ref<boolean>\n error: Ref<string | null>\n page: Ref<number>\n hasMore: ComputedRef<boolean>\n // Sort\n sortKey: Ref<string>\n // Filter options\n experimentTypes: Ref<ExperimentTypeOption[]>\n projects: Ref<SelectOption<string>[]>\n // Grouped view\n groupedByProject: ComputedRef<[string, ExperimentSummary[]][]>\n // Methods\n fetch: () => Promise<void>\n loadMore: () => Promise<void>\n reset: () => void\n select: (experiment: ExperimentSummary) => void\n clear: () => void\n fetchFilterOptions: () => Promise<void>\n}\n\n/** Fetches a paginated, filtered experiment list from the platform API for picker and selector UIs. */\nexport function useExperimentSelector(\n options: UseExperimentSelectorOptions = {},\n): UseExperimentSelectorReturn {\n const { limit = 100, immediate = false, experimentType, apiBaseUrl } = options\n const platformBase = apiBaseUrl ?? getPlatformApiUrl()\n const api = useApi()\n\n const experiments = ref<ExperimentSummary[]>([])\n const total = ref(0)\n const selectedExperiment = ref<ExperimentSummary | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n const page = ref(0)\n\n // Sort: combined key like \"created_at:desc\"\n const sortKey = ref<string>('created_at:desc')\n\n // Filter option data (fetched once, cached)\n const experimentTypes = ref<ExperimentTypeOption[]>([])\n const projects = ref<SelectOption<string>[]>([])\n let filterOptionsFetched = false\n\n const hasMore = computed(() => experiments.value.length < total.value)\n\n const filters: ExperimentFilters = reactive({\n search: undefined,\n status: undefined,\n project: undefined,\n experimentType: undefined,\n datePreset: undefined,\n })\n\n function parseSortKey(): { sortBy: ExperimentSortField; sortOrder: 'asc' | 'desc' } {\n const [field, order] = sortKey.value.split(':')\n return {\n sortBy: (field || 'created_at') as ExperimentSortField,\n sortOrder: (order || 'desc') as 'asc' | 'desc',\n }\n }\n\n async function fetchExperiments(): Promise<void> {\n isLoading.value = true\n error.value = null\n try {\n const params = new URLSearchParams()\n // Priority: explicit option > platform context (single type) > filter dropdown > no filter\n const allowedTypes = getPlatformContext()?.allowedExperimentTypes\n const effectiveType = experimentType\n ?? (allowedTypes?.length === 1 ? allowedTypes[0] : undefined)\n ?? filters.experimentType\n ?? undefined\n if (effectiveType) params.set('experiment_type', effectiveType)\n if (filters.status) params.set('status', filters.status)\n if (filters.search) params.set('search', filters.search)\n if (filters.project) params.set('project', filters.project)\n\n // Sort params\n const { sortBy, sortOrder } = parseSortKey()\n params.set('sort_by', sortBy)\n params.set('sort_order', sortOrder)\n\n // Date preset → created_after\n if (filters.datePreset) {\n params.set('created_after', datePresetToISO(filters.datePreset))\n }\n\n params.set('limit', String(limit))\n params.set('skip', String(page.value * limit))\n\n const query = params.toString()\n const base = platformBase ?? ''\n const url = `${base}/experiments${query ? `?${query}` : ''}`\n const data = await api.get<ExperimentListResponse>(url)\n\n // Client-side filter for multiple allowed types (backend only supports single type)\n let filtered = data.experiments\n if (!effectiveType && allowedTypes && allowedTypes.length > 1) {\n const typeSet = new Set(allowedTypes)\n filtered = filtered.filter(e => typeSet.has(e.experiment_type))\n }\n\n if (page.value === 0) {\n experiments.value = filtered\n } else {\n experiments.value = [...experiments.value, ...filtered]\n }\n // When client-side filtering is active (multiple allowedTypes), we can't\n // use data.total since it counts all types. Check if server has more pages.\n if (!effectiveType && allowedTypes && allowedTypes.length > 1) {\n if (data.experiments.length < limit) {\n // Server returned less than a full page — no more data\n total.value = experiments.value.length\n } else {\n // Might be more pages on the server\n total.value = experiments.value.length + 1\n }\n } else {\n total.value = data.total\n }\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to fetch experiments'\n if (page.value === 0) {\n experiments.value = []\n total.value = 0\n }\n } finally {\n isLoading.value = false\n }\n }\n\n async function fetchFilterOptions(): Promise<void> {\n if (filterOptionsFetched) return\n filterOptionsFetched = true\n\n const base = platformBase ?? ''\n const [typesRes, projectsRes] = await Promise.allSettled([\n api.get<Array<{ value: string; label: string; color?: string }>>(`${base}/experiments/experiment-types`),\n api.get<{ projects: Array<{ id: number; name: string }>; total: number }>(`${base}/projects`),\n ])\n\n if (typesRes.status === 'fulfilled' && Array.isArray(typesRes.value)) {\n experimentTypes.value = typesRes.value.map(t => ({\n value: t.value,\n label: t.label,\n color: t.color,\n }))\n }\n\n if (projectsRes.status === 'fulfilled' && projectsRes.value?.projects && Array.isArray(projectsRes.value.projects)) {\n projects.value = projectsRes.value.projects.map(p => ({\n value: p.name,\n label: p.name,\n }))\n }\n }\n\n async function loadMore(): Promise<void> {\n if (!hasMore.value || isLoading.value) return\n page.value++\n await fetchExperiments()\n }\n\n function reset(): void {\n page.value = 0\n experiments.value = []\n total.value = 0\n fetchExperiments()\n }\n\n function select(experiment: ExperimentSummary): void {\n selectedExperiment.value = experiment\n }\n\n function clear(): void {\n selectedExperiment.value = null\n filters.search = undefined\n filters.status = undefined\n filters.project = undefined\n filters.experimentType = undefined\n filters.datePreset = undefined\n sortKey.value = 'created_at:desc'\n page.value = 0\n }\n\n // Group experiments by project (client-side)\n const groupedByProject = computed<[string, ExperimentSummary[]][]>(() => {\n const groups = new Map<string, ExperimentSummary[]>()\n for (const exp of experiments.value) {\n const key = exp.project_name ?? exp.project ?? 'No project'\n const list = groups.get(key)\n if (list) {\n list.push(exp)\n } else {\n groups.set(key, [exp])\n }\n }\n // Sort alphabetically, \"No project\" last\n return [...groups.entries()].sort(([a], [b]) => {\n if (a === 'No project') return 1\n if (b === 'No project') return -1\n return a.localeCompare(b)\n })\n })\n\n // Debounced watch on search filter\n let debounceTimer: ReturnType<typeof setTimeout> | null = null\n watch(\n () => filters.search,\n () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = setTimeout(() => {\n page.value = 0\n fetchExperiments()\n }, 300)\n },\n )\n\n // Immediate watch on discrete filters and sort (cancel any pending search debounce)\n watch(\n () => [filters.status, filters.project, filters.experimentType, filters.datePreset, sortKey.value],\n () => {\n if (debounceTimer) clearTimeout(debounceTimer)\n debounceTimer = null\n page.value = 0\n fetchExperiments()\n },\n )\n\n onScopeDispose(() => {\n if (debounceTimer) clearTimeout(debounceTimer)\n })\n\n if (immediate) {\n fetchExperiments()\n }\n\n return {\n experiments,\n total,\n selectedExperiment,\n filters,\n isLoading,\n error,\n page,\n hasMore,\n sortKey,\n experimentTypes,\n projects,\n groupedByProject,\n fetch: fetchExperiments,\n loadMore,\n reset,\n select,\n clear,\n fetchFilterOptions,\n }\n}\n","import { ref, computed, onMounted, onUnmounted } from 'vue'\nimport type { PlatformContext, PlatformContextOptions, PlatformEvent } from '../types'\n\nconst DEFAULT_CONTEXT: PlatformContext = {\n isIntegrated: false,\n theme: 'system',\n}\n\nconst platformContext = ref<PlatformContext>({ ...DEFAULT_CONTEXT })\n\n// Track allowed origins for postMessage security.\n// Inferred origins come from platform injection and URL params.\n// Consumer origins and allowAnyOrigin are tracked per composable instance.\nlet inferredOrigins: Set<string> = new Set()\nlet allowedOrigins: Set<string> = new Set()\nlet allowAnyOrigin = false\nlet initialized = false\nlet listenerCount = 0\nlet nextConsumerId = 0\nlet currentHandler: ((event: MessageEvent) => void) | null = null\nconst consumerOrigins: Map<number, Set<string>> = new Map()\nconst allowAnyOriginConsumers: Set<number> = new Set()\n\n/**\n * Derive origin from URL (protocol + host)\n */\nfunction getOriginFromUrl(url: string): string | null {\n try {\n const parsed = new URL(url)\n return parsed.origin\n } catch {\n return null\n }\n}\n\nfunction normalizeAllowedOrigins(origins: string[] | undefined): Set<string> {\n const normalized = new Set<string>()\n if (!origins) {\n return normalized\n }\n\n for (const origin of origins) {\n normalized.add(getOriginFromUrl(origin) || origin)\n }\n return normalized\n}\n\nfunction recomputeOriginPolicy(): void {\n allowedOrigins = new Set(inferredOrigins)\n for (const origins of consumerOrigins.values()) {\n for (const origin of origins) {\n allowedOrigins.add(origin)\n }\n }\n allowAnyOrigin = allowAnyOriginConsumers.size > 0\n}\n\nfunction resetPlatformContextState(): void {\n inferredOrigins = new Set()\n allowedOrigins = new Set()\n allowAnyOrigin = false\n initialized = false\n listenerCount = 0\n currentHandler = null\n consumerOrigins.clear()\n allowAnyOriginConsumers.clear()\n platformContext.value = { ...DEFAULT_CONTEXT }\n}\n\n/**\n * Check if an origin is allowed for postMessage communication\n */\nfunction isOriginAllowed(origin: string): boolean {\n // Development mode: allow any origin (must be explicitly enabled)\n if (allowAnyOrigin) {\n console.warn('[MLD SDK] postMessage origin validation disabled - only use in development')\n return true\n }\n\n // Same origin is always allowed\n if (origin === window.location.origin) {\n return true\n }\n\n // Check against allowed origins list\n return allowedOrigins.has(origin)\n}\n\n/**\n * Platform context composable for plugin integration with MLD Platform.\n *\n * Provides secure communication with the parent platform via postMessage.\n *\n * @param options - Configuration options\n * @param options.allowedOrigins - List of allowed origins for postMessage\n * @param options.allowAnyOrigin - Allow any origin (UNSAFE, development only)\n *\n * @example\n * ```typescript\n * // Basic usage - derives origin from platform injection\n * const { isIntegrated, user, theme } = usePlatformContext()\n *\n * // With explicit allowed origins\n * const { isIntegrated } = usePlatformContext({\n * allowedOrigins: ['https://mld.example.com']\n * })\n *\n * // Development mode (UNSAFE)\n * const { isIntegrated } = usePlatformContext({\n * allowAnyOrigin: import.meta.env.DEV\n * })\n * ```\n */\n/** Connects a plugin to the MLD platform via postMessage, exposing user, theme, and experiment context. */\nexport function usePlatformContext(options: PlatformContextOptions = {}) {\n const consumerId = ++nextConsumerId\n const instanceOrigins = normalizeAllowedOrigins(options.allowedOrigins)\n const instanceAllowAnyOrigin = options.allowAnyOrigin === true\n\n function detectPlatform(): void {\n const detectedOrigins = new Set<string>()\n\n // Check if running under MLD Platform by looking for platform-injected global\n const platformData = (window as unknown as { __MINT_PLATFORM__?: PlatformContext }).__MINT_PLATFORM__\n\n if (platformData) {\n platformContext.value = {\n ...platformData,\n isIntegrated: true,\n }\n\n // Derive platform origin from injected data\n if (platformData.platformOrigin) {\n detectedOrigins.add(platformData.platformOrigin)\n } else if (platformData.platformApiUrl) {\n const origin = getOriginFromUrl(platformData.platformApiUrl)\n if (origin) {\n detectedOrigins.add(origin)\n }\n }\n } else {\n // Check for platform indicator in URL or localStorage\n const urlParams = new URLSearchParams(window.location.search)\n const hasPluginParam = urlParams.has('mld-plugin')\n\n // Try to get platform origin from URL parameter\n const platformOrigin = urlParams.get('mld-origin')\n if (platformOrigin) {\n const origin = getOriginFromUrl(platformOrigin)\n if (origin) {\n detectedOrigins.add(origin)\n }\n }\n\n platformContext.value = {\n isIntegrated: hasPluginParam,\n theme: (() => {\n try {\n const s = localStorage.getItem('mld-settings')\n if (s) return (JSON.parse(s).theme as 'light' | 'dark' | 'system') || 'system'\n } catch { /* ignore */ }\n return 'system'\n })(),\n platformOrigin: platformOrigin || undefined,\n }\n }\n\n inferredOrigins = detectedOrigins\n }\n\n function handlePlatformMessage(event: MessageEvent): void {\n // Only accept messages from parent window (platform)\n if (event.source !== window.parent) return\n\n // Validate origin for security\n if (!isOriginAllowed(event.origin)) {\n console.warn(`[MLD SDK] Rejected postMessage from untrusted origin: ${event.origin}`)\n return\n }\n\n try {\n const platformEvent = event.data as PlatformEvent\n if (!platformEvent.type?.startsWith('mld:')) return\n\n switch (platformEvent.type) {\n case 'mld:theme-changed':\n platformContext.value.theme = platformEvent.payload as 'light' | 'dark' | 'system'\n break\n case 'mld:user-changed':\n platformContext.value.user = platformEvent.payload as PlatformContext['user']\n break\n }\n } catch {\n // Ignore invalid messages\n }\n }\n\n /**\n * Send a message to the parent platform.\n * Uses validated target origin for security.\n */\n function sendToPlatform(event: PlatformEvent): void {\n if (!platformContext.value.isIntegrated || window.parent === window) {\n return\n }\n\n // Determine target origin\n let targetOrigin: string\n\n if (platformContext.value.platformOrigin) {\n // Use explicitly configured platform origin\n targetOrigin = platformContext.value.platformOrigin\n } else if (allowedOrigins.size > 0) {\n // Use first allowed origin (typically the platform)\n targetOrigin = allowedOrigins.values().next().value as string\n } else if (allowAnyOrigin) {\n // Development mode fallback\n targetOrigin = '*'\n console.warn('[MLD SDK] Using wildcard origin for postMessage - only use in development')\n } else {\n // Safety: if no origin is configured, log warning and don't send\n console.warn('[MLD SDK] Cannot send postMessage: no platform origin configured')\n return\n }\n\n window.parent.postMessage(event, targetOrigin)\n }\n\n /**\n * Request navigation to a path in the platform.\n */\n function navigate(path: string): void {\n sendToPlatform({\n type: 'mld:navigate',\n payload: path,\n })\n }\n\n /**\n * Show a notification in the platform.\n */\n function notify(message: string, type: 'success' | 'error' | 'warning' | 'info' = 'info'): void {\n sendToPlatform({\n type: 'mld:notification',\n payload: { message, type },\n })\n }\n\n onMounted(() => {\n consumerOrigins.set(consumerId, instanceOrigins)\n if (instanceAllowAnyOrigin) {\n allowAnyOriginConsumers.add(consumerId)\n }\n\n if (!initialized) {\n detectPlatform()\n currentHandler = handlePlatformMessage\n window.addEventListener('message', handlePlatformMessage)\n initialized = true\n }\n\n listenerCount++\n recomputeOriginPolicy()\n })\n\n onUnmounted(() => {\n consumerOrigins.delete(consumerId)\n allowAnyOriginConsumers.delete(consumerId)\n\n listenerCount = Math.max(0, listenerCount - 1)\n if (listenerCount === 0) {\n if (currentHandler) {\n window.removeEventListener('message', currentHandler)\n }\n resetPlatformContextState()\n return\n }\n\n recomputeOriginPolicy()\n })\n\n const isIntegrated = computed(() => platformContext.value.isIntegrated)\n const plugin = computed(() => platformContext.value.plugin)\n const user = computed(() => platformContext.value.user)\n const theme = computed(() => platformContext.value.theme)\n const features = computed(() => platformContext.value.features)\n\n return {\n context: platformContext,\n isIntegrated,\n plugin,\n user,\n theme,\n features,\n navigate,\n notify,\n sendToPlatform,\n }\n}\n","import {\n ref,\n computed,\n readonly,\n provide,\n toValue,\n onScopeDispose,\n type Ref,\n type ComputedRef,\n type InjectionKey,\n} from 'vue'\nimport type { ExperimentSummary, ExperimentStatus } from '../types'\n\nexport interface UseAppExperimentOptions {\n onSelect?: (experiment: ExperimentSummary) => void | Promise<void>\n onSave?: () => string | null | Promise<string | null>\n onDetach?: () => void\n saveDisabled?: Ref<boolean> | ComputedRef<boolean>\n saveDisabledMessage?: string | Ref<string | undefined> | ComputedRef<string | undefined>\n}\n\nexport interface UseAppExperimentReturn {\n set: (experiment: Pick<ExperimentSummary, 'id' | 'name' | 'status' | 'experiment_code'>) => void\n clear: () => void\n experimentName: Readonly<Ref<string | undefined>>\n experimentCode: Readonly<Ref<string | undefined>>\n experimentId: Readonly<Ref<number | null>>\n}\n\nexport interface AppExperimentState {\n experimentName: Ref<string | undefined>\n experimentCode: Ref<string | undefined>\n experimentStatus: Ref<ExperimentStatus | undefined>\n experimentId: Ref<number | null>\n showModal: Ref<boolean>\n saveLoading: Ref<boolean>\n saveSuccessMessage: Ref<string | undefined>\n showSave: Ref<boolean>\n showDetach: ComputedRef<boolean>\n closeModal: () => void\n saveDisabled: ComputedRef<boolean>\n saveDisabledMessage: ComputedRef<string | undefined>\n openModal: () => void\n handleSelect: (experiment: ExperimentSummary) => void\n handleSave: () => Promise<void>\n handleDetach: () => void\n}\n\nexport const APP_EXPERIMENT_KEY: InjectionKey<AppExperimentState> = Symbol('app-experiment')\n\n/** Manages the active experiment selection, save flow, and detach action for a plugin's app shell. */\nexport function useAppExperiment(options: UseAppExperimentOptions = {}): UseAppExperimentReturn {\n const experimentName = ref<string | undefined>()\n const experimentCode = ref<string | undefined>()\n const experimentStatus = ref<ExperimentStatus | undefined>()\n const experimentId = ref<number | null>(null)\n const showModal = ref(false)\n const saveLoading = ref(false)\n const saveSuccessMessage = ref<string | undefined>()\n\n let successTimer: ReturnType<typeof setTimeout> | null = null\n\n const showSave = ref(!!options.onSave)\n const showDetach = computed(() => experimentId.value !== null)\n const saveDisabled = computed(() => toValue(options.saveDisabled) ?? false)\n const saveDisabledMessage = computed(() => toValue(options.saveDisabledMessage))\n\n function set(experiment: Pick<ExperimentSummary, 'id' | 'name' | 'status' | 'experiment_code'>) {\n experimentId.value = experiment.id\n experimentName.value = experiment.name\n // Fallback: if no explicit experiment_code, derive `EXP-${id}` so the\n // topbar always has a compact identifier to show instead of the long\n // name. Matches the `EXP-${id}` format used elsewhere (e.g., queue\n // cards in mld-ms-planner). Callers with the real code should pass it\n // via `experiment_code` to keep the topbar chip in sync with the\n // formal code shown in the popover panel.\n experimentCode.value = experiment.experiment_code ?? (\n experiment.id != null ? `EXP-${experiment.id}` : undefined\n )\n experimentStatus.value = experiment.status\n }\n\n function clear() {\n experimentId.value = null\n experimentName.value = undefined\n experimentCode.value = undefined\n experimentStatus.value = undefined\n }\n\n function openModal() {\n showModal.value = true\n }\n\n function closeModal() {\n showModal.value = false\n }\n\n function handleSelect(experiment: ExperimentSummary) {\n set(experiment)\n showModal.value = false\n options.onSelect?.(experiment)\n }\n\n async function handleSave() {\n if (!options.onSave || saveLoading.value) return\n saveLoading.value = true\n try {\n const message = await options.onSave()\n if (message) {\n saveSuccessMessage.value = message\n if (successTimer) clearTimeout(successTimer)\n successTimer = setTimeout(() => {\n saveSuccessMessage.value = undefined\n successTimer = null\n }, 3000)\n }\n } finally {\n saveLoading.value = false\n }\n }\n\n function handleDetach() {\n clear()\n options.onDetach?.()\n }\n\n onScopeDispose(() => {\n if (successTimer) clearTimeout(successTimer)\n })\n\n const state: AppExperimentState = {\n experimentName,\n experimentCode,\n experimentStatus,\n experimentId,\n showModal,\n saveLoading,\n saveSuccessMessage,\n showSave,\n showDetach,\n saveDisabled,\n saveDisabledMessage,\n openModal,\n closeModal,\n handleSelect,\n handleSave,\n handleDetach,\n }\n\n provide(APP_EXPERIMENT_KEY, state)\n\n return {\n set,\n clear,\n experimentName: readonly(experimentName),\n experimentCode: readonly(experimentCode),\n experimentId: readonly(experimentId),\n }\n}\n","import { ref, computed, type Ref, type ComputedRef } from 'vue'\nimport type { Rack, SlotPosition, WellPlateFormat, Well } from '../types'\n\nexport interface UseRackEditorOptions {\n defaultFormat?: WellPlateFormat\n defaultInjectionVolume?: number\n maxRacks?: number\n minRacks?: number\n}\n\nexport interface UseRackEditorReturn {\n racks: Ref<Rack[]>\n activeRack: ComputedRef<Rack | undefined>\n activeRackId: Ref<string>\n\n addRack(name?: string): Rack\n removeRack(rackId: string): void\n reorderRacks(fromIndex: number, toIndex: number): void\n updateRack(rackId: string, data: Partial<Rack>): void\n setActiveRack(rackId: string): void\n\n setWellData(rackId: string, wellId: string, data: Partial<Well>): void\n clearWell(rackId: string, wellId: string): void\n clearAllWells(rackId: string): void\n fillSeries(rackId: string, prefix?: string): void\n\n getAllWells(): Array<{ rackId: string; wellId: string; well: Partial<Well> }>\n totalSampleCount: ComputedRef<number>\n reset(): void\n}\n\nconst SLOT_CYCLE: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\nconst PLATE_CONFIGS: Record<WellPlateFormat, { rows: number; cols: number }> = {\n 6: { rows: 2, cols: 3 },\n 12: { rows: 3, cols: 4 },\n 24: { rows: 4, cols: 6 },\n 48: { rows: 6, cols: 8 },\n 54: { rows: 6, cols: 9 },\n 96: { rows: 8, cols: 12 },\n 384: { rows: 16, cols: 24 },\n}\n\nlet globalIdCounter = 0\n\nfunction generateId(): string {\n globalIdCounter++\n return `rack-${Date.now()}-${globalIdCounter}`\n}\n\n/** Manages a set of sample racks with well-level assignment, series fill, and multi-rack reordering. */\nexport function useRackEditor(\n initialRacks?: Rack[],\n options?: UseRackEditorOptions,\n): UseRackEditorReturn {\n const defaultFormat = options?.defaultFormat ?? 54\n const defaultInjVol = options?.defaultInjectionVolume ?? 5\n const maxRacks = options?.maxRacks ?? 10\n const minRacks = options?.minRacks ?? 1\n\n function createDefaultRack(name?: string, slotIndex?: number): Rack {\n const slot = SLOT_CYCLE[(slotIndex ?? 0) % SLOT_CYCLE.length]!\n return {\n id: generateId(),\n name: name ?? `Rack ${racks.value.length + 1}`,\n format: defaultFormat,\n slot,\n injectionVolume: defaultInjVol,\n wells: {},\n }\n }\n\n const racks = ref<Rack[]>(\n initialRacks && initialRacks.length > 0\n ? initialRacks.map(r => ({ ...r }))\n : [createDefaultRack('Rack 1', 0)]\n ) as Ref<Rack[]>\n\n const activeRackId = ref<string>(racks.value[0]?.id ?? '')\n\n const activeRack = computed(() =>\n racks.value.find(r => r.id === activeRackId.value) ?? racks.value[0]\n )\n\n function addRack(name?: string): Rack {\n if (racks.value.length >= maxRacks) {\n return racks.value[racks.value.length - 1]!\n }\n const rack = createDefaultRack(name, racks.value.length)\n racks.value.push(rack)\n activeRackId.value = rack.id\n return rack\n }\n\n function removeRack(rackId: string): void {\n if (racks.value.length <= minRacks) return\n const index = racks.value.findIndex(r => r.id === rackId)\n if (index === -1) return\n racks.value.splice(index, 1)\n if (activeRackId.value === rackId && racks.value[0]) {\n activeRackId.value = racks.value[0].id\n }\n }\n\n function reorderRacks(fromIndex: number, toIndex: number): void {\n if (fromIndex < 0 || fromIndex >= racks.value.length) return\n if (toIndex < 0 || toIndex >= racks.value.length) return\n if (fromIndex === toIndex) return\n const [moved] = racks.value.splice(fromIndex, 1)\n if (moved) {\n racks.value.splice(toIndex, 0, moved)\n }\n }\n\n function updateRack(rackId: string, data: Partial<Rack>): void {\n const rack = racks.value.find(r => r.id === rackId)\n if (!rack) return\n if (data.name !== undefined) rack.name = data.name\n if (data.format !== undefined) rack.format = data.format\n if (data.slot !== undefined) rack.slot = data.slot\n if (data.injectionVolume !== undefined) rack.injectionVolume = data.injectionVolume\n if (data.wells !== undefined) rack.wells = data.wells\n }\n\n function setActiveRack(rackId: string): void {\n if (racks.value.some(r => r.id === rackId)) {\n activeRackId.value = rackId\n }\n }\n\n function setWellData(rackId: string, wellId: string, data: Partial<Well>): void {\n const rack = racks.value.find(r => r.id === rackId)\n if (!rack) return\n rack.wells[wellId] = { ...rack.wells[wellId], ...data }\n }\n\n function clearWell(rackId: string, wellId: string): void {\n const rack = racks.value.find(r => r.id === rackId)\n if (!rack) return\n delete rack.wells[wellId]\n }\n\n function clearAllWells(rackId: string): void {\n const rack = racks.value.find(r => r.id === rackId)\n if (!rack) return\n rack.wells = {}\n }\n\n function fillSeries(rackId: string, prefix: string = 'S'): void {\n const rack = racks.value.find(r => r.id === rackId)\n if (!rack) return\n\n const config = PLATE_CONFIGS[rack.format]\n if (!config) return\n\n let counter = 1\n for (let row = 0; row < config.rows; row++) {\n for (let col = 0; col < config.cols; col++) {\n const rowLabel = String.fromCharCode(65 + row)\n const wellId = `${rowLabel}${col + 1}`\n // Only fill empty wells\n if (!rack.wells[wellId]?.sampleType) {\n const paddedNum = String(counter).padStart(3, '0')\n rack.wells[wellId] = {\n id: wellId,\n row,\n col,\n state: 'filled',\n sampleType: 'sample',\n metadata: {\n label: `${prefix}${paddedNum}`,\n injectionVolume: rack.injectionVolume,\n injectionCount: 1,\n },\n }\n }\n counter++\n }\n }\n }\n\n function getAllWells(): Array<{ rackId: string; wellId: string; well: Partial<Well> }> {\n const result: Array<{ rackId: string; wellId: string; well: Partial<Well> }> = []\n for (const rack of racks.value) {\n for (const [wellId, well] of Object.entries(rack.wells)) {\n result.push({ rackId: rack.id, wellId, well })\n }\n }\n return result\n }\n\n const totalSampleCount = computed(() => {\n let count = 0\n for (const rack of racks.value) {\n count += Object.keys(rack.wells).length\n }\n return count\n })\n\n function reset(): void {\n racks.value = [createDefaultRack('Rack 1', 0)]\n activeRackId.value = racks.value[0]!.id\n }\n\n return {\n racks,\n activeRack,\n activeRackId,\n addRack,\n removeRack,\n reorderRacks,\n updateRack,\n setActiveRack,\n setWellData,\n clearWell,\n clearAllWells,\n fillSeries,\n getAllWells,\n totalSampleCount,\n reset,\n }\n}\n","import { ref, computed, type ComputedRef } from 'vue'\nimport type {\n PlateMapEditorState,\n PlateMap,\n SampleType,\n WellPlateFormat,\n} from '../types'\n\nconst DEFAULT_PALETTE = [\n '#3B82F6', // blue\n '#10B981', // green\n '#EF4444', // red\n '#F59E0B', // amber\n '#8B5CF6', // purple\n '#F97316', // orange\n '#06B6D4', // cyan\n '#14B8A6', // teal\n '#6B7280', // gray\n]\n\nconst MAX_HISTORY = 50\n\ninterface HistoryEntry {\n plates: PlateMap[]\n samples: SampleType[]\n}\n\nexport interface UseWellPlateEditorOptions {\n maxHistory?: number\n defaultFormat?: WellPlateFormat\n}\n\nexport interface UseWellPlateEditorReturn {\n state: ComputedRef<PlateMapEditorState>\n plates: ComputedRef<PlateMap[]>\n activePlate: ComputedRef<PlateMap | undefined>\n samples: ComputedRef<SampleType[]>\n selectedWells: ComputedRef<string[]>\n activeSampleId: ComputedRef<string | undefined>\n canUndo: ComputedRef<boolean>\n canRedo: ComputedRef<boolean>\n setActivePlate: (plateId: string) => void\n setActiveSample: (sampleId: string | undefined) => void\n setSelectedWells: (wellIds: string[]) => void\n addPlate: (name?: string, format?: WellPlateFormat) => PlateMap\n removePlate: (plateId: string) => void\n addSample: (name: string, color?: string) => SampleType\n removeSample: (sampleId: string) => void\n assignSample: (wellIds: string[], sampleId: string | undefined) => void\n clearWells: (wellIds: string[]) => void\n undo: () => void\n redo: () => void\n exportData: (format: 'json' | 'csv') => string\n importData: (data: string, format: 'json' | 'csv') => boolean\n loadState: (state: Partial<PlateMapEditorState>) => void\n reset: () => void\n}\n\nfunction createEmptyPlate(\n name: string,\n format: WellPlateFormat,\n id?: string\n): PlateMap {\n return {\n id: id || `plate-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n name,\n format,\n wells: {},\n }\n}\n\nfunction generateSampleId(): string {\n return `sample-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n/** Manages multi-plate well-plate state with sample assignment, selection, and undo/redo history. */\nexport function useWellPlateEditor(\n initialState?: Partial<PlateMapEditorState>,\n options: UseWellPlateEditorOptions = {}\n): UseWellPlateEditorReturn {\n const { maxHistory = MAX_HISTORY, defaultFormat = 96 } = options\n\n const defaultPlate = createEmptyPlate('Plate 1', defaultFormat)\n\n const internalState = ref<PlateMapEditorState>({\n plates: initialState?.plates || [defaultPlate],\n activePlateId: initialState?.activePlateId || defaultPlate.id,\n samples: initialState?.samples || [],\n selectedWells: initialState?.selectedWells || [],\n activeSampleId: initialState?.activeSampleId,\n })\n\n const history = ref<HistoryEntry[]>([])\n const historyIndex = ref(-1)\n\n const state = computed(() => internalState.value)\n const plates = computed(() => internalState.value.plates)\n const activePlate = computed(() =>\n internalState.value.plates.find(p => p.id === internalState.value.activePlateId)\n )\n const samples = computed(() => internalState.value.samples)\n const selectedWells = computed(() => internalState.value.selectedWells)\n const activeSampleId = computed(() => internalState.value.activeSampleId)\n\n const canUndo = computed(() => historyIndex.value >= 0)\n const canRedo = computed(() => historyIndex.value < history.value.length - 1)\n\n function saveToHistory() {\n const entry: HistoryEntry = {\n plates: structuredClone(internalState.value.plates),\n samples: structuredClone(internalState.value.samples),\n }\n\n if (historyIndex.value < history.value.length - 1) {\n history.value = history.value.slice(0, historyIndex.value + 1)\n }\n\n history.value.push(entry)\n\n if (history.value.length > maxHistory) {\n history.value.shift()\n } else {\n historyIndex.value++\n }\n }\n\n function setActivePlate(plateId: string) {\n if (internalState.value.plates.some(p => p.id === plateId)) {\n internalState.value.activePlateId = plateId\n internalState.value.selectedWells = []\n }\n }\n\n function setActiveSample(sampleId: string | undefined) {\n internalState.value.activeSampleId = sampleId\n }\n\n function setSelectedWells(wellIds: string[]) {\n internalState.value.selectedWells = wellIds\n }\n\n function addPlate(name?: string, format?: WellPlateFormat): PlateMap {\n saveToHistory()\n const plateNumber = internalState.value.plates.length + 1\n const plate = createEmptyPlate(\n name || `Plate ${plateNumber}`,\n format || defaultFormat\n )\n internalState.value.plates.push(plate)\n internalState.value.activePlateId = plate.id\n internalState.value.selectedWells = []\n return plate\n }\n\n function removePlate(plateId: string) {\n if (internalState.value.plates.length <= 1) return\n\n saveToHistory()\n const index = internalState.value.plates.findIndex(p => p.id === plateId)\n if (index === -1) return\n\n internalState.value.plates.splice(index, 1)\n if (internalState.value.activePlateId === plateId) {\n internalState.value.activePlateId = internalState.value.plates[0].id\n internalState.value.selectedWells = []\n }\n }\n\n function addSample(name: string, color?: string): SampleType {\n saveToHistory()\n const sample: SampleType = {\n id: generateSampleId(),\n name,\n color: color || DEFAULT_PALETTE[internalState.value.samples.length % DEFAULT_PALETTE.length],\n count: 0,\n }\n internalState.value.samples.push(sample)\n return sample\n }\n\n function removeSample(sampleId: string) {\n saveToHistory()\n const index = internalState.value.samples.findIndex(s => s.id === sampleId)\n if (index === -1) return\n\n internalState.value.samples.splice(index, 1)\n\n for (const plate of internalState.value.plates) {\n for (const well of Object.values(plate.wells)) {\n if (well.sampleType === sampleId) {\n delete well.sampleType\n well.state = 'empty'\n }\n }\n }\n\n if (internalState.value.activeSampleId === sampleId) {\n internalState.value.activeSampleId = undefined\n }\n\n updateSampleCounts()\n }\n\n function assignSample(wellIds: string[], sampleId: string | undefined) {\n if (wellIds.length === 0) return\n\n saveToHistory()\n const plate = activePlate.value\n if (!plate) return\n\n for (const wellId of wellIds) {\n const well = plate.wells[wellId] || {\n id: wellId,\n row: wellId.charCodeAt(0) - 65,\n col: parseInt(wellId.slice(1)) - 1,\n state: 'empty',\n }\n\n if (sampleId) {\n well.sampleType = sampleId\n well.state = 'filled'\n } else {\n delete well.sampleType\n well.state = 'empty'\n }\n\n plate.wells[wellId] = well\n }\n updateSampleCounts()\n }\n\n function clearWells(wellIds: string[]) {\n assignSample(wellIds, undefined)\n }\n\n function updateSampleCounts() {\n const counts: Record<string, number> = {}\n\n for (const plate of internalState.value.plates) {\n for (const well of Object.values(plate.wells)) {\n if (well.sampleType) {\n counts[well.sampleType] = (counts[well.sampleType] || 0) + 1\n }\n }\n }\n\n for (const sample of internalState.value.samples) {\n sample.count = counts[sample.id] || 0\n }\n }\n\n function undo() {\n if (!canUndo.value) return\n\n const entry = history.value[historyIndex.value]\n historyIndex.value--\n\n internalState.value.plates = structuredClone(entry.plates)\n internalState.value.samples = structuredClone(entry.samples)\n\n const activePlateExists = internalState.value.plates.some(p => p.id === internalState.value.activePlateId)\n if (!activePlateExists) {\n internalState.value.activePlateId = internalState.value.plates[0]?.id || ''\n }\n internalState.value.selectedWells = []\n }\n\n function redo() {\n if (!canRedo.value) return\n\n historyIndex.value++\n const entry = history.value[historyIndex.value]\n\n internalState.value.plates = structuredClone(entry.plates)\n internalState.value.samples = structuredClone(entry.samples)\n internalState.value.selectedWells = []\n }\n\n function exportData(format: 'json' | 'csv'): string {\n if (format === 'json') {\n return JSON.stringify({\n plates: internalState.value.plates,\n samples: internalState.value.samples,\n }, null, 2)\n }\n\n const sampleMap = new Map(internalState.value.samples.map(s => [s.id, s.name]))\n const rows = ['Plate,Well,Sample Type,Sample Name']\n\n for (const plate of internalState.value.plates) {\n for (const [wellId, well] of Object.entries(plate.wells)) {\n if (well.sampleType) {\n const sampleName = sampleMap.get(well.sampleType) || ''\n rows.push(`\"${plate.name}\",\"${wellId}\",\"${well.sampleType}\",\"${sampleName}\"`)\n }\n }\n }\n\n return rows.join('\\n')\n }\n\n function importData(data: string, format: 'json' | 'csv'): boolean {\n try {\n saveToHistory()\n\n if (format === 'json') {\n const parsed = JSON.parse(data)\n if (parsed.plates && Array.isArray(parsed.plates)) {\n internalState.value.plates = parsed.plates\n internalState.value.activePlateId = parsed.plates[0]?.id || ''\n }\n if (parsed.samples && Array.isArray(parsed.samples)) {\n internalState.value.samples = parsed.samples\n }\n internalState.value.selectedWells = []\n updateSampleCounts()\n return true\n }\n\n const lines = data.trim().split('\\n')\n if (lines.length < 2) return false\n\n const plateMap = new Map<string, PlateMap>()\n const sampleMap = new Map<string, SampleType>()\n\n for (let i = 1; i < lines.length; i++) {\n const parts = lines[i].match(/(?:[^\",]+|\"[^\"]*\")+/g)\n if (!parts || parts.length < 4) continue\n\n const plateName = parts[0].replace(/^\"|\"$/g, '')\n const wellId = parts[1].replace(/^\"|\"$/g, '')\n const sampleId = parts[2].replace(/^\"|\"$/g, '')\n const sampleName = parts[3].replace(/^\"|\"$/g, '')\n\n if (!plateMap.has(plateName)) {\n plateMap.set(plateName, createEmptyPlate(plateName, defaultFormat))\n }\n\n if (sampleId && !sampleMap.has(sampleId)) {\n sampleMap.set(sampleId, {\n id: sampleId,\n name: sampleName || sampleId,\n color: DEFAULT_PALETTE[sampleMap.size % DEFAULT_PALETTE.length],\n count: 0,\n })\n }\n\n const plate = plateMap.get(plateName)!\n plate.wells[wellId] = {\n id: wellId,\n row: wellId.charCodeAt(0) - 65,\n col: parseInt(wellId.slice(1)) - 1,\n state: sampleId ? 'filled' : 'empty',\n sampleType: sampleId || undefined,\n }\n }\n\n internalState.value.plates = Array.from(plateMap.values())\n internalState.value.samples = Array.from(sampleMap.values())\n internalState.value.activePlateId = internalState.value.plates[0]?.id || ''\n internalState.value.selectedWells = []\n updateSampleCounts()\n return true\n } catch {\n return false\n }\n }\n\n function loadState(state: Partial<PlateMapEditorState>) {\n saveToHistory()\n if (state.plates && state.plates.length > 0) {\n internalState.value.plates = structuredClone(state.plates)\n internalState.value.activePlateId = state.activePlateId ?? state.plates[0].id\n }\n if (state.samples) {\n internalState.value.samples = structuredClone(state.samples)\n }\n internalState.value.selectedWells = state.selectedWells ?? []\n internalState.value.activeSampleId = state.activeSampleId\n updateSampleCounts()\n }\n\n function reset() {\n const plate = createEmptyPlate('Plate 1', defaultFormat)\n internalState.value = {\n plates: [plate],\n activePlateId: plate.id,\n samples: [],\n selectedWells: [],\n activeSampleId: undefined,\n }\n history.value = []\n historyIndex.value = -1\n }\n\n return {\n state,\n plates,\n activePlate,\n samples,\n selectedWells,\n activeSampleId,\n canUndo,\n canRedo,\n setActivePlate,\n setActiveSample,\n setSelectedWells,\n addPlate,\n removePlate,\n addSample,\n removeSample,\n assignSample,\n clearWells,\n undo,\n redo,\n exportData,\n importData,\n loadState,\n reset,\n }\n}\n","import { ref, computed } from 'vue'\nimport type {\n InputMode,\n OutlierAction,\n OutlierInfo,\n ColumnInfo,\n MetadataRow,\n AutoGroupResult,\n ParsedCsvData,\n} from '../types/auto-group'\nimport type { SampleGroup } from '../types/components'\n\nexport const DEFAULT_COLORS = [\n '#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',\n '#EC4899', '#06B6D4', '#84CC16', '#F97316', '#6366F1',\n]\n\nconst DELIMITER_CANDIDATES = ['_', '-', '.'] as const\n\n// --- Pure functions (exported for testing) ---\n\nexport function analyzeDelimiter(lines: string[]): {\n delimiter: string\n dominantFieldCount: number\n minFieldCount: number\n consistency: number\n} {\n if (lines.length === 0) {\n return { delimiter: '_', dominantFieldCount: 1, minFieldCount: 1, consistency: 0 }\n }\n\n let bestDelimiter = '_'\n let bestConsistency = -1\n let bestFieldCount = 1\n\n for (const candidate of DELIMITER_CANDIDATES) {\n const fieldCounts = lines.map(line => line.split(candidate).length)\n const countFrequency = new Map<number, number>()\n\n for (const count of fieldCounts) {\n countFrequency.set(count, (countFrequency.get(count) ?? 0) + 1)\n }\n\n // Find mode (most frequent field count)\n let modeCount = 1\n let modeFrequency = 0\n for (const [count, freq] of countFrequency) {\n if (freq > modeFrequency || (freq === modeFrequency && count > modeCount)) {\n modeCount = count\n modeFrequency = freq\n }\n }\n\n const rawConsistency = modeFrequency / lines.length\n // A delimiter that produces field count 1 didn't actually split anything\n const consistency = modeCount > 1 ? rawConsistency : 0\n\n if (\n consistency > bestConsistency ||\n (consistency === bestConsistency &&\n DELIMITER_CANDIDATES.indexOf(candidate) < DELIMITER_CANDIDATES.indexOf(bestDelimiter as typeof candidate))\n ) {\n bestDelimiter = candidate\n bestConsistency = consistency\n bestFieldCount = modeCount\n }\n }\n\n const bestFieldCounts = lines.map(line => line.split(bestDelimiter).length)\n const multiFieldCounts = bestFieldCounts.filter(c => c >= 2)\n const minFieldCount = multiFieldCounts.length > 0 ? Math.min(...multiFieldCounts) : 1\n\n return {\n delimiter: bestDelimiter,\n dominantFieldCount: bestFieldCount,\n minFieldCount,\n consistency: bestConsistency,\n }\n}\n\nexport function detectOutliers(\n lines: string[],\n delimiter: string,\n minFieldCount: number,\n): OutlierInfo[] {\n const outliers: OutlierInfo[] = []\n\n for (let i = 0; i < lines.length; i++) {\n const fieldCount = lines[i].split(delimiter).length\n if (fieldCount < minFieldCount) {\n outliers.push({\n sample: lines[i],\n index: i,\n fieldCount,\n action: 'include',\n })\n }\n }\n\n return outliers\n}\n\nconst QC_KEYWORDS = new Set([\n 'eqc', 'iqc', 'qc', 'blank', 'std', 'standard', 'test',\n])\n\nexport function classifyOutlierAction(\n sample: string,\n delimiter: string,\n): OutlierAction {\n const segments = sample.split(delimiter)\n return segments.some(seg => QC_KEYWORDS.has(seg.toLowerCase()))\n ? 'qc'\n : 'include'\n}\n\n// A column is useful for grouping when it has more than one distinct value\n// AND it does not produce a unique value per row (which would create N\n// singleton groups instead of meaningful aggregation).\nexport function isUsefulField(field: ColumnInfo, rowCount: number): boolean {\n return field.cardinality > 1 && !(rowCount > 1 && field.cardinality === rowCount)\n}\n\nexport function extractColumns(\n samples: string[],\n delimiter: string,\n minFieldCount: number,\n): ColumnInfo[] {\n if (samples.length === 0) return []\n\n const suffixCount = minFieldCount - 1\n const rows = samples.map(s => {\n const parts = s.split(delimiter)\n const splitAt = parts.length - suffixCount\n return [\n parts.slice(0, splitAt).join(delimiter),\n ...parts.slice(splitAt),\n ]\n })\n\n const columnCount = minFieldCount\n const columns: ColumnInfo[] = []\n for (let col = 0; col < columnCount; col++) {\n const values = rows.map(row => row[col])\n const unique = [...new Set(values)]\n columns.push({\n index: col,\n name: col === 0 ? 'Condition' : `Field ${col + 1}`,\n uniqueValues: unique,\n cardinality: unique.length,\n type: col === 0 ? 'prefix' : 'suffix',\n })\n }\n\n return columns\n}\n\nexport function parseCSVLine(line: string, delimiter: string = ','): string[] {\n const result: string[] = []\n let current = ''\n let inQuotes = false\n\n for (let i = 0; i < line.length; i++) {\n const char = line[i]\n if (char === '\"') {\n inQuotes = !inQuotes\n } else if (char === delimiter && !inQuotes) {\n result.push(current.trim())\n current = ''\n } else {\n current += char\n }\n }\n result.push(current.trim())\n\n return result\n}\n\nexport function parseCSV(text: string): ParsedCsvData {\n const lines = text.trim().split('\\n')\n if (lines.length < 2) {\n throw new Error('CSV must have at least a header and one data row')\n }\n\n // Auto-detect delimiter: tab takes precedence if present in header\n const firstLine = lines[0]\n const csvDelimiter = firstLine.includes('\\t') ? '\\t' : ','\n\n const headers = parseCSVLine(firstLine, csvDelimiter)\n const rows: Record<string, string>[] = []\n\n for (let i = 1; i < lines.length; i++) {\n const values = parseCSVLine(lines[i], csvDelimiter)\n if (values.length !== headers.length) continue\n const row: Record<string, string> = {}\n headers.forEach((header, idx) => {\n row[header] = values[idx]\n })\n rows.push(row)\n }\n\n // Auto-detect sample column\n const sampleKeywords = ['sample', 'name', 'id', 'sample_name', 'samplename', 'file name', 'filename', 'file_name']\n const sampleColumn =\n headers.find(h => sampleKeywords.includes(h.toLowerCase())) ?? headers[0]\n\n return { columns: headers, rows, sampleColumn, delimiter: csvDelimiter }\n}\n\nexport function computeGroups(\n allSamples: string[],\n columns: ColumnInfo[],\n enabledFields: Set<number>,\n outlierActions: Map<number, OutlierAction>,\n delimiter: string,\n minFieldCount: number,\n): { groups: SampleGroup[]; metadata: MetadataRow[]; excludedSamples: string[] } {\n const excludedSamples: string[] = []\n const qcSamples: string[] = []\n const conformingSamples: string[] = []\n\n for (let i = 0; i < allSamples.length; i++) {\n const action = outlierActions.get(i)\n if (action === 'exclude') {\n excludedSamples.push(allSamples[i])\n } else if (action === 'qc') {\n qcSamples.push(allSamples[i])\n } else {\n conformingSamples.push(allSamples[i])\n }\n }\n\n // Build group map\n const groupMap = new Map<string, string[]>()\n const metadata: MetadataRow[] = []\n const enabledIndices = [...enabledFields].sort((a, b) => a - b)\n\n const suffixCount = minFieldCount - 1\n\n for (const sample of conformingSamples) {\n const parts = sample.split(delimiter)\n const splitAt = Math.max(1, parts.length - suffixCount)\n const row = [\n parts.slice(0, splitAt).join(delimiter),\n ...parts.slice(splitAt),\n ]\n\n // Build group key from enabled columns\n const keyParts: string[] = []\n for (const idx of enabledIndices) {\n if (idx < row.length && idx < columns.length) {\n keyParts.push(row[idx])\n }\n }\n const groupKey = keyParts.join(' / ')\n\n if (!groupMap.has(groupKey)) {\n groupMap.set(groupKey, [])\n }\n groupMap.get(groupKey)!.push(sample)\n\n // Build metadata row with ALL columns\n const fields: Record<string, string> = {}\n for (const col of columns) {\n if (col.index < row.length) {\n fields[col.name] = row[col.index]\n }\n }\n metadata.push({ sampleName: sample, fields, group: groupKey })\n }\n\n // Convert to SampleGroup[]\n const groups: SampleGroup[] = []\n let colorIdx = 0\n for (const [name, samples] of groupMap) {\n groups.push({\n name,\n color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],\n samples,\n })\n colorIdx++\n }\n\n // QC group\n if (qcSamples.length > 0) {\n groups.push({\n name: 'QC',\n color: '#6B7280',\n samples: qcSamples,\n })\n for (const sample of qcSamples) {\n metadata.push({ sampleName: sample, fields: {}, group: 'QC' })\n }\n }\n\n return { groups, metadata, excludedSamples }\n}\n\n/**\n * Extract sample metadata from raw design_data into ParsedCsvData format.\n *\n * Looks for a `samples` array in the design data. For each sample, merges\n * the `conditions` dict (the metadata table) with the `sample_name` to\n * produce a flat tabular row. QC and blank samples are filtered out by\n * their explicit `sample_type` field.\n *\n * Returns null if no samples with conditions are found.\n */\nexport function extractSamplesFromDesignData(\n rawData: Record<string, unknown>,\n): ParsedCsvData | null {\n const samples = rawData.samples\n if (!Array.isArray(samples) || samples.length === 0) return null\n\n // Single pass: filter QC/blank and collect all condition keys\n const allConditionKeys: string[] = []\n const keySet = new Set<string>()\n const filteredSamples: Record<string, unknown>[] = []\n\n for (const sample of samples) {\n const sampleType = String((sample as Record<string, unknown>).sample_type ?? 'sample').toLowerCase()\n if (sampleType === 'qc' || sampleType === 'blank') continue\n\n filteredSamples.push(sample as Record<string, unknown>)\n const conditions = (sample as Record<string, unknown>).conditions as Record<string, string> | undefined\n if (conditions && typeof conditions === 'object') {\n for (const key of Object.keys(conditions)) {\n if (!keySet.has(key)) {\n keySet.add(key)\n allConditionKeys.push(key)\n }\n }\n }\n }\n\n if (filteredSamples.length === 0 || allConditionKeys.length === 0) return null\n\n const columns = ['sample_name', ...allConditionKeys]\n const rows: Record<string, string>[] = filteredSamples.map((sample) => {\n const conditions = (sample.conditions as Record<string, string>) ?? {}\n const row: Record<string, string> = {\n sample_name: String(sample.sample_name ?? ''),\n }\n for (const key of allConditionKeys) {\n row[key] = conditions[key] ?? ''\n }\n return row\n })\n\n return { columns, rows, sampleColumn: 'sample_name', delimiter: ',' }\n}\n\nexport function computeGroupsFromCsv(\n csvData: ParsedCsvData,\n columns: ColumnInfo[],\n enabledFields: Set<number>,\n): { groups: SampleGroup[]; metadata: MetadataRow[]; excludedSamples: string[] } {\n const groupMap = new Map<string, string[]>()\n const metadata: MetadataRow[] = []\n const enabledCols = columns\n .filter(c => enabledFields.has(c.index))\n .sort((a, b) => a.index - b.index)\n\n for (const row of csvData.rows) {\n const sampleName = row[csvData.sampleColumn]\n\n // Build group key from enabled CSV column values\n // Use originalName for CSV row lookup (survives user renames), display name for group key\n const keyParts = enabledCols.map(col => row[col.originalName ?? col.name])\n const groupKey = keyParts.join(' / ')\n\n if (!groupMap.has(groupKey)) {\n groupMap.set(groupKey, [])\n }\n groupMap.get(groupKey)!.push(sampleName)\n\n // Build metadata row with ALL columns — use display name as key, original for lookup\n const fields: Record<string, string> = {}\n for (const col of columns) {\n fields[col.name] = row[col.originalName ?? col.name]\n }\n metadata.push({ sampleName, fields, group: groupKey })\n }\n\n // Convert to SampleGroup[]\n const groups: SampleGroup[] = []\n let colorIdx = 0\n for (const [name, samples] of groupMap) {\n groups.push({\n name,\n color: DEFAULT_COLORS[colorIdx % DEFAULT_COLORS.length],\n samples,\n })\n colorIdx++\n }\n\n return { groups, metadata, excludedSamples: [] }\n}\n\n// --- Reactive composable ---\n\n/** Parses sample names or CSV data to propose group assignments with outlier detection and preview. */\nexport function useAutoGroup() {\n const inputMode = ref<InputMode>('paste')\n const rawText = ref('')\n const csvData = ref<ParsedCsvData | null>(null)\n const delimiter = ref('_')\n const dominantFieldCount = ref(1)\n const minFieldCount = ref(1)\n const outliers = ref<OutlierInfo[]>([])\n const fields = ref<ColumnInfo[]>([])\n const fieldNames = ref<Record<number, string>>({})\n const enabledFields = ref(new Set<number>())\n\n const isTabularMode = computed(() =>\n (inputMode.value === 'csv' || inputMode.value === 'experiment') && csvData.value !== null,\n )\n\n const samples = computed(() => {\n if (isTabularMode.value && csvData.value) {\n return csvData.value.rows.map(r => r[csvData.value!.sampleColumn])\n }\n return rawText.value\n .split('\\n')\n .map(l => l.trim())\n .filter(l => l.length > 0)\n })\n\n const hasOutliers = computed(() => outliers.value.length > 0)\n\n const conformingSamples = computed(() => {\n const outlierIndices = new Set(outliers.value.map(o => o.index))\n return samples.value.filter((_, i) => !outlierIndices.has(i))\n })\n\n const outlierActions = computed(() => {\n const map = new Map<number, OutlierAction>()\n for (const o of outliers.value) {\n map.set(o.index, o.action)\n }\n return map\n })\n\n const effectiveColumns = computed(() => {\n return fields.value.map(col => ({\n ...col,\n name: fieldNames.value[col.index] ?? col.name,\n }))\n })\n\n const _computedResult = computed((): AutoGroupResult => {\n if (effectiveColumns.value.length === 0 || enabledFields.value.size === 0) {\n return { groups: [], metadata: [], excludedSamples: [] }\n }\n\n if (isTabularMode.value && csvData.value) {\n return computeGroupsFromCsv(\n csvData.value,\n effectiveColumns.value,\n enabledFields.value,\n )\n }\n\n return computeGroups(\n samples.value,\n effectiveColumns.value,\n enabledFields.value,\n outlierActions.value,\n delimiter.value,\n minFieldCount.value,\n )\n })\n\n const groups = computed(() => _computedResult.value.groups)\n const metadata = computed(() => _computedResult.value.metadata)\n const excludedSamples = computed(() => _computedResult.value.excludedSamples)\n\n const allSingletons = computed(() =>\n groups.value.length > 1 && groups.value.every(g => g.samples.length === 1),\n )\n\n const result = _computedResult\n\n function parseInput() {\n if (isTabularMode.value) {\n parseCsvInput()\n } else {\n parsePasteInput()\n }\n }\n\n function parsePasteInput() {\n const lines = samples.value\n if (lines.length === 0) return\n\n const analysis = analyzeDelimiter(lines)\n delimiter.value = analysis.delimiter\n dominantFieldCount.value = analysis.dominantFieldCount\n\n // Use dominantFieldCount as outlier threshold so QC/test samples with\n // fewer fields than the majority are correctly flagged\n outliers.value = detectOutliers(lines, analysis.delimiter, analysis.dominantFieldCount)\n\n // Apply smart default actions: auto-classify QC/test samples\n for (const outlier of outliers.value) {\n outlier.action = classifyOutlierAction(outlier.sample, analysis.delimiter)\n }\n\n const conforming = lines.filter(\n (_, i) => !outliers.value.some(o => o.index === i)\n )\n\n // Recompute minFieldCount from conforming samples only\n const conformingFieldCounts = conforming.map(s => s.split(analysis.delimiter).length)\n minFieldCount.value = conformingFieldCounts.length > 0\n ? Math.min(...conformingFieldCounts)\n : analysis.dominantFieldCount\n\n fields.value = extractColumns(conforming, analysis.delimiter, minFieldCount.value)\n\n fieldNames.value = {}\n const rowCount = conforming.length\n enabledFields.value = new Set(\n fields.value.filter(f => isUsefulField(f, rowCount)).map(f => f.index),\n )\n }\n\n function parseCsvInput() {\n if (!csvData.value) return\n\n const csv = csvData.value\n const nonSampleCols = csv.columns.filter(c => c !== csv.sampleColumn)\n\n fields.value = nonSampleCols.map((col, i) => {\n const values = csv.rows.map(r => r[col])\n const unique = [...new Set(values)]\n return {\n index: i,\n name: col,\n originalName: col,\n uniqueValues: unique,\n cardinality: unique.length,\n }\n })\n\n // For CSV, no outliers\n outliers.value = []\n delimiter.value = csv.delimiter\n dominantFieldCount.value = csv.columns.length\n\n fieldNames.value = Object.fromEntries(fields.value.map(f => [f.index, f.name]))\n const rowCount = csv.rows.length\n enabledFields.value = new Set(\n fields.value.filter(f => isUsefulField(f, rowCount)).map(f => f.index),\n )\n }\n\n function setOutlierAction(index: number, action: OutlierAction) {\n const outlier = outliers.value.find(o => o.index === index)\n if (outlier) {\n outlier.action = action\n // Trigger reactivity\n outliers.value = [...outliers.value]\n }\n }\n\n function setAllOutlierActions(action: OutlierAction) {\n for (const outlier of outliers.value) {\n outlier.action = action\n }\n outliers.value = [...outliers.value]\n }\n\n function toggleField(index: number) {\n const newSet = new Set(enabledFields.value)\n if (newSet.has(index)) {\n newSet.delete(index)\n } else {\n newSet.add(index)\n }\n enabledFields.value = newSet\n }\n\n function renameField(index: number, name: string) {\n fieldNames.value = { ...fieldNames.value, [index]: name }\n }\n\n function loadExperimentData(rawData: Record<string, unknown>): boolean {\n const parsed = extractSamplesFromDesignData(rawData)\n if (!parsed) return false\n\n inputMode.value = 'experiment'\n csvData.value = parsed\n parseCsvInput()\n return true\n }\n\n function reset() {\n rawText.value = ''\n csvData.value = null\n delimiter.value = '_'\n dominantFieldCount.value = 1\n minFieldCount.value = 1\n outliers.value = []\n fields.value = []\n fieldNames.value = {}\n enabledFields.value = new Set()\n }\n\n return {\n // State\n inputMode,\n rawText,\n csvData,\n delimiter,\n dominantFieldCount,\n minFieldCount,\n outliers,\n fields,\n fieldNames,\n enabledFields,\n // Computed\n samples,\n hasOutliers,\n conformingSamples,\n groups,\n metadata,\n excludedSamples,\n allSingletons,\n result,\n effectiveColumns,\n // Actions\n parseInput,\n loadExperimentData,\n setOutlierAction,\n setAllOutlierActions,\n toggleField,\n renameField,\n reset,\n }\n}\n","<script setup lang=\"ts\">\n/** Interactive chemical structure editor powered by JSME, emitting SMILES strings for compound registration in experiment design. */\nimport { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { MoleculeData } from '../types'\n\ninterface Props {\n modelValue?: MoleculeData\n disabled?: boolean\n readonly?: boolean\n height?: number\n showSmiles?: boolean\n placeholder?: string\n error?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n readonly: false,\n height: 300,\n showSmiles: true,\n placeholder: 'Draw a chemical structure',\n error: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [data: MoleculeData | undefined]\n 'error': [message: string]\n}>()\n\n// State\nconst containerRef = ref<HTMLDivElement | null>(null)\nconst jsmeInstance = ref<unknown>(null)\nconst isLoading = ref(true)\nconst loadError = ref<string | null>(null)\nconst debounceTimer = ref<ReturnType<typeof setTimeout> | null>(null)\n\n// Computed\nconst hasStructure = computed(() => {\n return props.modelValue?.smiles && props.modelValue.smiles.length > 0\n})\n\n// Use window-level state to persist across component instances and hot-reloads\ninterface JSMEGlobalState {\n __jsmeCallbacks__?: Array<() => void>\n __jsmeLoading__?: boolean\n __jsmeLoaded__?: boolean\n __jsmeLoadPromise__?: Promise<void>\n JSApplet?: { JSME?: new (id: string, width: string, height: string, options?: object) => unknown }\n jsmeOnLoad?: () => void\n}\n\nfunction getJSMEState(): JSMEGlobalState {\n const win = window as unknown as JSMEGlobalState\n if (!win.__jsmeCallbacks__) {\n win.__jsmeCallbacks__ = []\n }\n return win\n}\n\nfunction waitForJSME(): Promise<void> {\n const win = getJSMEState()\n\n // Already loaded\n if (win.JSApplet?.JSME) {\n return Promise.resolve()\n }\n\n // Reuse existing promise if loading\n if (win.__jsmeLoadPromise__) {\n return win.__jsmeLoadPromise__\n }\n\n win.__jsmeLoadPromise__ = new Promise((resolve, reject) => {\n // Double-check after promise creation\n if (win.JSApplet?.JSME) {\n resolve()\n return\n }\n\n // Set up global callback FIRST (before checking for existing script)\n const originalOnLoad = win.jsmeOnLoad\n win.jsmeOnLoad = () => {\n win.__jsmeLoaded__ = true\n originalOnLoad?.()\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n\n // Add to callback queue\n win.__jsmeCallbacks__?.push(resolve)\n\n // Check if script already exists\n const existingScript = document.querySelector('script[data-jsme]')\n if (existingScript) {\n // Script exists, poll for JSApplet.JSME (in case jsmeOnLoad already fired)\n const checkReady = setInterval(() => {\n if (win.JSApplet?.JSME) {\n clearInterval(checkReady)\n win.__jsmeLoaded__ = true\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n }, 100)\n\n setTimeout(() => {\n clearInterval(checkReady)\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n return\n }\n\n // Load the script\n win.__jsmeLoading__ = true\n const script = document.createElement('script')\n script.src = 'https://jsme-editor.github.io/dist/jsme/jsme.nocache.js'\n script.integrity = 'sha384-l6tNzsc/eAJ7uql0dGAcHYI5ANVEV7DrJYjzXp3t13L+3OzLnfpzJO0Uio7mUSjY'\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute('data-jsme', 'true')\n\n script.onerror = () => {\n win.__jsmeLoading__ = false\n win.__jsmeLoadPromise__ = undefined\n reject(new Error('Failed to load JSME script'))\n }\n\n // Timeout\n setTimeout(() => {\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n\n document.head.appendChild(script)\n })\n\n return win.__jsmeLoadPromise__\n}\n\n// Wait for the browser to complete a paint cycle\nfunction waitForPaint(): Promise<void> {\n return new Promise(resolve => {\n requestAnimationFrame(() => requestAnimationFrame(() => resolve()))\n })\n}\n\n// JSME initialization — two phases:\n// 1. Load the script (no DOM needed)\n// 2. Reveal the editor div, wait for paint, then mount JSME with explicit px dimensions\nasync function initJSME() {\n if (props.readonly) return\n\n try {\n isLoading.value = true\n loadError.value = null\n\n // Phase 1: load the JSME script (independent of DOM)\n await waitForJSME()\n\n const win = getJSMEState()\n if (!win.JSApplet?.JSME) {\n throw new Error('JSME library not available after loading')\n }\n\n // Phase 2: reveal the editor div by clearing the loading state\n isLoading.value = false\n await nextTick()\n\n if (!containerRef.value) return\n\n // Wait for a full paint cycle so GWT can measure the container\n await waitForPaint()\n\n if (!containerRef.value) return\n\n // Use explicit pixel width — GWT cannot resolve percentage widths reliably\n const rect = containerRef.value.getBoundingClientRect()\n const width = Math.floor(rect.width) || 400\n\n // Mount JSME into the now-painted container\n const editorId = `jsme-${Date.now()}`\n containerRef.value.id = editorId\n\n const instance = new win.JSApplet.JSME(\n editorId,\n `${width}px`,\n `${props.height}px`,\n {\n options: 'query,hydrogens,paste',\n }\n )\n\n jsmeInstance.value = instance\n\n // Set initial value if provided\n if (props.modelValue?.molfile) {\n (instance as { readMolFile: (mol: string) => void }).readMolFile(props.modelValue.molfile)\n }\n\n // Set up change callback\n (instance as { setCallBack: (event: string, callback: () => void) => void }).setCallBack('AfterStructureModified', handleStructureChange)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to load molecule editor'\n loadError.value = message\n emit('error', message)\n isLoading.value = false\n }\n}\n\nfunction handleStructureChange() {\n // Debounce the change event\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n\n debounceTimer.value = setTimeout(() => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n molFile: () => string\n }\n\n const smiles = instance.smiles()\n const molfile = instance.molFile()\n\n if (!smiles || smiles.length === 0) {\n emit('update:modelValue', undefined)\n } else {\n emit('update:modelValue', { smiles, molfile })\n }\n }, 300)\n}\n\nfunction clearStructure() {\n if (jsmeInstance.value) {\n (jsmeInstance.value as { reset: () => void }).reset()\n }\n emit('update:modelValue', undefined)\n}\n\n// Watch for external value changes\nwatch(() => props.modelValue, (newValue) => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n readMolFile: (mol: string) => void\n reset: () => void\n }\n\n // Only update if the external value differs from current\n const currentSmiles = instance.smiles()\n if (newValue?.smiles !== currentSmiles) {\n if (newValue?.molfile) {\n instance.readMolFile(newValue.molfile)\n } else {\n instance.reset()\n }\n }\n})\n\n// Lifecycle\nonMounted(() => {\n if (!props.readonly) {\n initJSME()\n } else {\n isLoading.value = false\n }\n})\n\nonUnmounted(() => {\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n jsmeInstance.value = null\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-molecule-input',\n disabled ? 'mld-molecule-input--disabled' : '',\n readonly ? 'mld-molecule-input--readonly' : '',\n error ? 'mld-molecule-input--error' : '',\n ]\"\n >\n <!-- Loading state -->\n <div\n v-if=\"isLoading && !readonly\"\n class=\"mld-molecule-input__skeleton\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__skeleton-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-molecule-input__skeleton-text\">Loading molecule editor...</span>\n </div>\n\n <!-- Error state -->\n <div\n v-else-if=\"loadError\"\n class=\"mld-molecule-input__error\"\n >\n <svg\n class=\"mld-molecule-input__error-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n {{ loadError }}\n </div>\n\n <!-- Readonly mode - show empty or placeholder -->\n <template v-else-if=\"readonly\">\n <div\n v-if=\"hasStructure\"\n class=\"mld-molecule-input__readonly\"\n :style=\"{ height: `${height}px` }\"\n >\n <!-- In readonly mode, we just display a placeholder since we don't have SVG rendering -->\n <div class=\"mld-molecule-input__empty\">\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-molecule-input__empty-text\">Structure defined (readonly)</span>\n </div>\n </div>\n <div\n v-else\n class=\"mld-molecule-input__empty\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-molecule-input__empty-text\">No structure</span>\n </div>\n </template>\n\n <!-- Editor mode -->\n <template v-else>\n <div\n ref=\"containerRef\"\n class=\"mld-molecule-input__editor\"\n :style=\"{ height: `${height}px` }\"\n role=\"application\"\n aria-label=\"Molecule structure editor\"\n />\n\n <!-- Actions toolbar -->\n <div class=\"mld-molecule-input__actions\">\n <button\n type=\"button\"\n class=\"mld-molecule-input__action-btn\"\n :disabled=\"!hasStructure || disabled\"\n aria-label=\"Clear structure\"\n @click=\"clearStructure\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </template>\n\n <!-- SMILES display -->\n <div\n v-if=\"showSmiles && hasStructure && !loadError\"\n class=\"mld-molecule-input__smiles\"\n >\n <span class=\"mld-molecule-input__smiles-label\">SMILES:</span>\n {{ modelValue?.smiles }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/molecule-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Interactive chemical structure editor powered by JSME, emitting SMILES strings for compound registration in experiment design. */\nimport { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'\nimport type { MoleculeData } from '../types'\n\ninterface Props {\n modelValue?: MoleculeData\n disabled?: boolean\n readonly?: boolean\n height?: number\n showSmiles?: boolean\n placeholder?: string\n error?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n readonly: false,\n height: 300,\n showSmiles: true,\n placeholder: 'Draw a chemical structure',\n error: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [data: MoleculeData | undefined]\n 'error': [message: string]\n}>()\n\n// State\nconst containerRef = ref<HTMLDivElement | null>(null)\nconst jsmeInstance = ref<unknown>(null)\nconst isLoading = ref(true)\nconst loadError = ref<string | null>(null)\nconst debounceTimer = ref<ReturnType<typeof setTimeout> | null>(null)\n\n// Computed\nconst hasStructure = computed(() => {\n return props.modelValue?.smiles && props.modelValue.smiles.length > 0\n})\n\n// Use window-level state to persist across component instances and hot-reloads\ninterface JSMEGlobalState {\n __jsmeCallbacks__?: Array<() => void>\n __jsmeLoading__?: boolean\n __jsmeLoaded__?: boolean\n __jsmeLoadPromise__?: Promise<void>\n JSApplet?: { JSME?: new (id: string, width: string, height: string, options?: object) => unknown }\n jsmeOnLoad?: () => void\n}\n\nfunction getJSMEState(): JSMEGlobalState {\n const win = window as unknown as JSMEGlobalState\n if (!win.__jsmeCallbacks__) {\n win.__jsmeCallbacks__ = []\n }\n return win\n}\n\nfunction waitForJSME(): Promise<void> {\n const win = getJSMEState()\n\n // Already loaded\n if (win.JSApplet?.JSME) {\n return Promise.resolve()\n }\n\n // Reuse existing promise if loading\n if (win.__jsmeLoadPromise__) {\n return win.__jsmeLoadPromise__\n }\n\n win.__jsmeLoadPromise__ = new Promise((resolve, reject) => {\n // Double-check after promise creation\n if (win.JSApplet?.JSME) {\n resolve()\n return\n }\n\n // Set up global callback FIRST (before checking for existing script)\n const originalOnLoad = win.jsmeOnLoad\n win.jsmeOnLoad = () => {\n win.__jsmeLoaded__ = true\n originalOnLoad?.()\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n\n // Add to callback queue\n win.__jsmeCallbacks__?.push(resolve)\n\n // Check if script already exists\n const existingScript = document.querySelector('script[data-jsme]')\n if (existingScript) {\n // Script exists, poll for JSApplet.JSME (in case jsmeOnLoad already fired)\n const checkReady = setInterval(() => {\n if (win.JSApplet?.JSME) {\n clearInterval(checkReady)\n win.__jsmeLoaded__ = true\n win.__jsmeCallbacks__?.forEach(cb => cb())\n win.__jsmeCallbacks__ = []\n resolve()\n }\n }, 100)\n\n setTimeout(() => {\n clearInterval(checkReady)\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n return\n }\n\n // Load the script\n win.__jsmeLoading__ = true\n const script = document.createElement('script')\n script.src = 'https://jsme-editor.github.io/dist/jsme/jsme.nocache.js'\n script.integrity = 'sha384-l6tNzsc/eAJ7uql0dGAcHYI5ANVEV7DrJYjzXp3t13L+3OzLnfpzJO0Uio7mUSjY'\n script.crossOrigin = 'anonymous'\n script.async = true\n script.setAttribute('data-jsme', 'true')\n\n script.onerror = () => {\n win.__jsmeLoading__ = false\n win.__jsmeLoadPromise__ = undefined\n reject(new Error('Failed to load JSME script'))\n }\n\n // Timeout\n setTimeout(() => {\n if (!win.__jsmeLoaded__ && !win.JSApplet?.JSME) {\n reject(new Error('JSME initialization timeout'))\n }\n }, 15000)\n\n document.head.appendChild(script)\n })\n\n return win.__jsmeLoadPromise__\n}\n\n// Wait for the browser to complete a paint cycle\nfunction waitForPaint(): Promise<void> {\n return new Promise(resolve => {\n requestAnimationFrame(() => requestAnimationFrame(() => resolve()))\n })\n}\n\n// JSME initialization — two phases:\n// 1. Load the script (no DOM needed)\n// 2. Reveal the editor div, wait for paint, then mount JSME with explicit px dimensions\nasync function initJSME() {\n if (props.readonly) return\n\n try {\n isLoading.value = true\n loadError.value = null\n\n // Phase 1: load the JSME script (independent of DOM)\n await waitForJSME()\n\n const win = getJSMEState()\n if (!win.JSApplet?.JSME) {\n throw new Error('JSME library not available after loading')\n }\n\n // Phase 2: reveal the editor div by clearing the loading state\n isLoading.value = false\n await nextTick()\n\n if (!containerRef.value) return\n\n // Wait for a full paint cycle so GWT can measure the container\n await waitForPaint()\n\n if (!containerRef.value) return\n\n // Use explicit pixel width — GWT cannot resolve percentage widths reliably\n const rect = containerRef.value.getBoundingClientRect()\n const width = Math.floor(rect.width) || 400\n\n // Mount JSME into the now-painted container\n const editorId = `jsme-${Date.now()}`\n containerRef.value.id = editorId\n\n const instance = new win.JSApplet.JSME(\n editorId,\n `${width}px`,\n `${props.height}px`,\n {\n options: 'query,hydrogens,paste',\n }\n )\n\n jsmeInstance.value = instance\n\n // Set initial value if provided\n if (props.modelValue?.molfile) {\n (instance as { readMolFile: (mol: string) => void }).readMolFile(props.modelValue.molfile)\n }\n\n // Set up change callback\n (instance as { setCallBack: (event: string, callback: () => void) => void }).setCallBack('AfterStructureModified', handleStructureChange)\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Failed to load molecule editor'\n loadError.value = message\n emit('error', message)\n isLoading.value = false\n }\n}\n\nfunction handleStructureChange() {\n // Debounce the change event\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n\n debounceTimer.value = setTimeout(() => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n molFile: () => string\n }\n\n const smiles = instance.smiles()\n const molfile = instance.molFile()\n\n if (!smiles || smiles.length === 0) {\n emit('update:modelValue', undefined)\n } else {\n emit('update:modelValue', { smiles, molfile })\n }\n }, 300)\n}\n\nfunction clearStructure() {\n if (jsmeInstance.value) {\n (jsmeInstance.value as { reset: () => void }).reset()\n }\n emit('update:modelValue', undefined)\n}\n\n// Watch for external value changes\nwatch(() => props.modelValue, (newValue) => {\n if (!jsmeInstance.value) return\n\n const instance = jsmeInstance.value as {\n smiles: () => string\n readMolFile: (mol: string) => void\n reset: () => void\n }\n\n // Only update if the external value differs from current\n const currentSmiles = instance.smiles()\n if (newValue?.smiles !== currentSmiles) {\n if (newValue?.molfile) {\n instance.readMolFile(newValue.molfile)\n } else {\n instance.reset()\n }\n }\n})\n\n// Lifecycle\nonMounted(() => {\n if (!props.readonly) {\n initJSME()\n } else {\n isLoading.value = false\n }\n})\n\nonUnmounted(() => {\n if (debounceTimer.value) {\n clearTimeout(debounceTimer.value)\n }\n jsmeInstance.value = null\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-molecule-input',\n disabled ? 'mld-molecule-input--disabled' : '',\n readonly ? 'mld-molecule-input--readonly' : '',\n error ? 'mld-molecule-input--error' : '',\n ]\"\n >\n <!-- Loading state -->\n <div\n v-if=\"isLoading && !readonly\"\n class=\"mld-molecule-input__skeleton\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__skeleton-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-molecule-input__skeleton-text\">Loading molecule editor...</span>\n </div>\n\n <!-- Error state -->\n <div\n v-else-if=\"loadError\"\n class=\"mld-molecule-input__error\"\n >\n <svg\n class=\"mld-molecule-input__error-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n {{ loadError }}\n </div>\n\n <!-- Readonly mode - show empty or placeholder -->\n <template v-else-if=\"readonly\">\n <div\n v-if=\"hasStructure\"\n class=\"mld-molecule-input__readonly\"\n :style=\"{ height: `${height}px` }\"\n >\n <!-- In readonly mode, we just display a placeholder since we don't have SVG rendering -->\n <div class=\"mld-molecule-input__empty\">\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-molecule-input__empty-text\">Structure defined (readonly)</span>\n </div>\n </div>\n <div\n v-else\n class=\"mld-molecule-input__empty\"\n :style=\"{ height: `${height}px` }\"\n >\n <svg\n class=\"mld-molecule-input__empty-icon\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n stroke-width=\"2\"\n d=\"M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z\"\n />\n </svg>\n <span class=\"mld-molecule-input__empty-text\">No structure</span>\n </div>\n </template>\n\n <!-- Editor mode -->\n <template v-else>\n <div\n ref=\"containerRef\"\n class=\"mld-molecule-input__editor\"\n :style=\"{ height: `${height}px` }\"\n role=\"application\"\n aria-label=\"Molecule structure editor\"\n />\n\n <!-- Actions toolbar -->\n <div class=\"mld-molecule-input__actions\">\n <button\n type=\"button\"\n class=\"mld-molecule-input__action-btn\"\n :disabled=\"!hasStructure || disabled\"\n aria-label=\"Clear structure\"\n @click=\"clearStructure\"\n >\n <svg fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n <path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\" />\n </svg>\n </button>\n </div>\n </template>\n\n <!-- SMILES display -->\n <div\n v-if=\"showSmiles && hasStructure && !loadError\"\n class=\"mld-molecule-input__smiles\"\n >\n <span class=\"mld-molecule-input__smiles-label\">SMILES:</span>\n {{ modelValue?.smiles }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/molecule-input.css';\n</style>\n","import { computed, type ComputedRef } from 'vue'\n\n// Concentration unit types\nexport type MolarityUnit = 'pM' | 'nM' | 'µM' | 'mM' | 'M'\nexport type MassVolumeUnit = 'pg/mL' | 'ng/mL' | 'µg/mL' | 'mg/mL' | 'g/mL'\nexport type PercentageUnit = '% v/v' | '% w/v' | '% w/w'\nexport type ConcentrationUnit = MolarityUnit | MassVolumeUnit | PercentageUnit | string\n\nexport interface ConcentrationValue {\n value: number\n unit: ConcentrationUnit\n}\n\nexport interface UnitCategory {\n label: string\n units: ConcentrationUnit[]\n}\n\nexport interface UseConcentrationUnitsReturn {\n unitCategories: ComputedRef<UnitCategory[]>\n molarityUnits: MolarityUnit[]\n massVolumeUnits: MassVolumeUnit[]\n percentageUnits: PercentageUnit[]\n convert: (value: number, from: ConcentrationUnit, to: ConcentrationUnit, mw?: number) => number | null\n formatWithUnit: (concentration: ConcentrationValue, precision?: number) => string\n parseConcentration: (input: string) => ConcentrationValue | null\n getBaseUnit: (unit: ConcentrationUnit) => ConcentrationUnit\n getConversionHint: (concentration: ConcentrationValue) => string | null\n isMolarity: (unit: ConcentrationUnit) => boolean\n isMassVolume: (unit: ConcentrationUnit) => boolean\n isPercentage: (unit: ConcentrationUnit) => boolean\n}\n\n// Conversion factors to base unit (M for molarity, g/mL for mass/volume)\nconst MOLARITY_FACTORS: Record<MolarityUnit, number> = {\n 'pM': 1e-12,\n 'nM': 1e-9,\n 'µM': 1e-6,\n 'mM': 1e-3,\n 'M': 1,\n}\n\nconst MASS_VOLUME_FACTORS: Record<MassVolumeUnit, number> = {\n 'pg/mL': 1e-12,\n 'ng/mL': 1e-9,\n 'µg/mL': 1e-6,\n 'mg/mL': 1e-3,\n 'g/mL': 1,\n}\n\n// Percentage conversion factors (for potential future use)\n// const PERCENTAGE_FACTORS: Record<PercentageUnit, number> = {\n// '% v/v': 0.01,\n// '% w/v': 0.01,\n// '% w/w': 0.01,\n// }\n\nconst MOLARITY_UNITS: MolarityUnit[] = ['pM', 'nM', 'µM', 'mM', 'M']\nconst MASS_VOLUME_UNITS: MassVolumeUnit[] = ['pg/mL', 'ng/mL', 'µg/mL', 'mg/mL', 'g/mL']\nconst PERCENTAGE_UNITS: PercentageUnit[] = ['% v/v', '% w/v', '% w/w']\n\n/** Converts between molarity, mass/volume, and percentage concentration units with formatting helpers. */\nexport function useConcentrationUnits(): UseConcentrationUnitsReturn {\n const unitCategories = computed<UnitCategory[]>(() => [\n { label: 'Molarity', units: MOLARITY_UNITS },\n { label: 'Mass/Volume', units: MASS_VOLUME_UNITS },\n { label: 'Percentage', units: PERCENTAGE_UNITS },\n ])\n\n function isMolarity(unit: ConcentrationUnit): unit is MolarityUnit {\n return MOLARITY_UNITS.includes(unit as MolarityUnit)\n }\n\n function isMassVolume(unit: ConcentrationUnit): unit is MassVolumeUnit {\n return MASS_VOLUME_UNITS.includes(unit as MassVolumeUnit)\n }\n\n function isPercentage(unit: ConcentrationUnit): unit is PercentageUnit {\n return PERCENTAGE_UNITS.includes(unit as PercentageUnit)\n }\n\n function getBaseUnit(unit: ConcentrationUnit): ConcentrationUnit {\n if (isMolarity(unit)) return 'M'\n if (isMassVolume(unit)) return 'g/mL'\n if (isPercentage(unit)) return '% w/v'\n return unit\n }\n\n function convert(\n value: number,\n from: ConcentrationUnit,\n to: ConcentrationUnit,\n mw?: number\n ): number | null {\n if (from === to) return value\n if (value === 0) return 0\n\n // Same category conversion\n if (isMolarity(from) && isMolarity(to)) {\n const baseValue = value * MOLARITY_FACTORS[from]\n return baseValue / MOLARITY_FACTORS[to]\n }\n\n if (isMassVolume(from) && isMassVolume(to)) {\n const baseValue = value * MASS_VOLUME_FACTORS[from]\n return baseValue / MASS_VOLUME_FACTORS[to]\n }\n\n if (isPercentage(from) && isPercentage(to)) {\n // Percentage conversions within same category are direct\n return value\n }\n\n // Cross-category conversion (requires molecular weight)\n if (mw && mw > 0) {\n // Molarity to Mass/Volume\n if (isMolarity(from) && isMassVolume(to)) {\n // Convert to M, then to g/mL using MW, then to target unit\n const molarConc = value * MOLARITY_FACTORS[from]\n const massConc = molarConc * mw // g/mL (MW in g/mol, concentration in mol/L = g/L)\n return massConc / MASS_VOLUME_FACTORS[to]\n }\n\n // Mass/Volume to Molarity\n if (isMassVolume(from) && isMolarity(to)) {\n // Convert to g/mL, then to M using MW, then to target unit\n const massConc = value * MASS_VOLUME_FACTORS[from]\n const molarConc = massConc / mw // M (g/mL / g/mol = mol/L)\n return molarConc / MOLARITY_FACTORS[to]\n }\n }\n\n return null // Cannot convert without MW or between incompatible units\n }\n\n function formatWithUnit(concentration: ConcentrationValue, precision: number = 3): string {\n const { value, unit } = concentration\n if (value === 0) return `0 ${unit}`\n\n // Use appropriate precision based on value magnitude\n let formattedValue: string\n if (Math.abs(value) >= 1000) {\n formattedValue = value.toExponential(precision - 1)\n } else if (Math.abs(value) < 0.001) {\n formattedValue = value.toExponential(precision - 1)\n } else {\n formattedValue = value.toPrecision(precision)\n }\n\n // Remove trailing zeros after decimal point\n formattedValue = formattedValue.replace(/\\.?0+$/, '')\n formattedValue = formattedValue.replace(/\\.?0+e/, 'e')\n\n return `${formattedValue} ${unit}`\n }\n\n function parseConcentration(input: string): ConcentrationValue | null {\n const trimmed = input.trim()\n if (!trimmed) return null\n\n // Match number followed by optional unit\n const match = trimmed.match(/^([-+]?\\d*\\.?\\d+(?:[eE][-+]?\\d+)?)\\s*(.*)$/)\n if (!match) return null\n\n const value = parseFloat(match[1])\n if (isNaN(value)) return null\n\n let unit = match[2].trim()\n if (!unit) {\n return null // Unit is required\n }\n\n // Normalize common variations\n unit = unit\n .replace(/uM/i, 'µM')\n .replace(/um/i, 'µM')\n .replace(/ug\\/ml/i, 'µg/mL')\n .replace(/ng\\/ml/i, 'ng/mL')\n .replace(/mg\\/ml/i, 'mg/mL')\n .replace(/pg\\/ml/i, 'pg/mL')\n .replace(/g\\/ml/i, 'g/mL')\n\n return { value, unit }\n }\n\n function getConversionHint(concentration: ConcentrationValue): string | null {\n const { value, unit } = concentration\n if (value === 0) return null\n\n // Find adjacent unit for conversion hint\n if (isMolarity(unit)) {\n const index = MOLARITY_UNITS.indexOf(unit)\n // If value is large, suggest next higher unit\n if (value >= 1000 && index < MOLARITY_UNITS.length - 1) {\n const nextUnit = MOLARITY_UNITS[index + 1]\n const converted = convert(value, unit, nextUnit)\n if (converted !== null) {\n return formatWithUnit({ value: converted, unit: nextUnit })\n }\n }\n // If value is small, suggest next lower unit\n if (value < 1 && index > 0) {\n const prevUnit = MOLARITY_UNITS[index - 1]\n const converted = convert(value, unit, prevUnit)\n if (converted !== null) {\n return formatWithUnit({ value: converted, unit: prevUnit })\n }\n }\n }\n\n if (isMassVolume(unit)) {\n const index = MASS_VOLUME_UNITS.indexOf(unit)\n if (value >= 1000 && index < MASS_VOLUME_UNITS.length - 1) {\n const nextUnit = MASS_VOLUME_UNITS[index + 1]\n const converted = convert(value, unit, nextUnit)\n if (converted !== null) {\n return formatWithUnit({ value: converted, unit: nextUnit })\n }\n }\n if (value < 1 && index > 0) {\n const prevUnit = MASS_VOLUME_UNITS[index - 1]\n const converted = convert(value, unit, prevUnit)\n if (converted !== null) {\n return formatWithUnit({ value: converted, unit: prevUnit })\n }\n }\n }\n\n return null\n }\n\n return {\n unitCategories,\n molarityUnits: MOLARITY_UNITS,\n massVolumeUnits: MASS_VOLUME_UNITS,\n percentageUnits: PERCENTAGE_UNITS,\n convert,\n formatWithUnit,\n parseConcentration,\n getBaseUnit,\n getConversionHint,\n isMolarity,\n isMassVolume,\n isPercentage,\n }\n}\n","<script setup lang=\"ts\">\n/** Paired numeric + unit input for reagent concentrations (pM–M, mg/mL, etc.) with optional molar-mass conversion hint. */\nimport { computed, watch } from 'vue'\nimport {\n useConcentrationUnits,\n type ConcentrationValue,\n type ConcentrationUnit,\n} from '../composables/useConcentrationUnits'\n\ninterface Props {\n modelValue?: ConcentrationValue\n allowedUnits?: ConcentrationUnit[]\n showConversion?: boolean\n molecularWeight?: number\n min?: number\n max?: number\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n placeholder?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showConversion: true,\n disabled: false,\n error: false,\n size: 'md',\n placeholder: 'Enter value',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ConcentrationValue | undefined]\n}>()\n\nconst { unitCategories, getConversionHint } = useConcentrationUnits()\n\n// Filter categories based on allowedUnits\nconst filteredCategories = computed(() => {\n if (!props.allowedUnits || props.allowedUnits.length === 0) {\n return unitCategories.value\n }\n return unitCategories.value\n .map(cat => ({\n label: cat.label,\n units: cat.units.filter(u => props.allowedUnits!.includes(u)),\n }))\n .filter(cat => cat.units.length > 0)\n})\n\n// Flatten all available units\nconst availableUnits = computed(() => {\n return filteredCategories.value.flatMap(cat => cat.units)\n})\n\n// Current value and unit\nconst currentValue = computed(() => props.modelValue?.value)\nconst currentUnit = computed(() => props.modelValue?.unit || availableUnits.value[0] || 'µM')\n\n// Conversion hint\nconst conversionHint = computed(() => {\n if (!props.showConversion || !props.modelValue) return null\n return getConversionHint(props.modelValue)\n})\n\nfunction handleValueInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = target.value === '' ? undefined : Number(target.value)\n\n if (value === undefined || isNaN(value)) {\n emit('update:modelValue', undefined)\n return\n }\n\n // Apply min/max constraints\n let clampedValue = value\n if (props.min !== undefined && clampedValue < props.min) {\n clampedValue = props.min\n }\n if (props.max !== undefined && clampedValue > props.max) {\n clampedValue = props.max\n }\n\n emit('update:modelValue', {\n value: clampedValue,\n unit: currentUnit.value,\n })\n}\n\nfunction handleUnitChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const unit = target.value as ConcentrationUnit\n\n emit('update:modelValue', {\n value: currentValue.value ?? 0,\n unit,\n })\n}\n\n// Ensure modelValue always has a valid unit\nwatch(availableUnits, (units) => {\n if (props.modelValue && !units.includes(props.modelValue.unit)) {\n emit('update:modelValue', {\n value: props.modelValue.value,\n unit: units[0] || 'µM',\n })\n }\n}, { immediate: true })\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-concentration-input',\n error ? 'mld-concentration-input--error' : '',\n disabled ? 'mld-concentration-input--disabled' : '',\n ]\"\n >\n <div :class=\"['mld-concentration-input__controls', `mld-concentration-input__controls--${size}`]\">\n <input\n type=\"number\"\n :value=\"currentValue\"\n :min=\"min\"\n :max=\"max\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n :class=\"[\n 'mld-concentration-input__value',\n `mld-concentration-input__value--${size}`,\n disabled ? 'mld-concentration-input__value--disabled' : '',\n ]\"\n aria-label=\"Concentration value\"\n @input=\"handleValueInput\"\n />\n\n <div class=\"mld-concentration-input__unit\">\n <select\n :value=\"currentUnit\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-concentration-input__unit-select',\n `mld-concentration-input__unit-select--${size}`,\n ]\"\n aria-label=\"Concentration unit\"\n @change=\"handleUnitChange\"\n >\n <template v-for=\"category in filteredCategories\" :key=\"category.label\">\n <optgroup\n v-if=\"filteredCategories.length > 1\"\n :label=\"category.label\"\n class=\"mld-concentration-input__unit-group\"\n >\n <option\n v-for=\"unit in category.units\"\n :key=\"unit\"\n :value=\"unit\"\n >\n {{ unit }}\n </option>\n </optgroup>\n <template v-else>\n <option\n v-for=\"unit in category.units\"\n :key=\"unit\"\n :value=\"unit\"\n >\n {{ unit }}\n </option>\n </template>\n </template>\n </select>\n </div>\n </div>\n\n <div\n v-if=\"showConversion && conversionHint\"\n class=\"mld-concentration-input__conversion\"\n >\n {{ conversionHint }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/concentration-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Paired numeric + unit input for reagent concentrations (pM–M, mg/mL, etc.) with optional molar-mass conversion hint. */\nimport { computed, watch } from 'vue'\nimport {\n useConcentrationUnits,\n type ConcentrationValue,\n type ConcentrationUnit,\n} from '../composables/useConcentrationUnits'\n\ninterface Props {\n modelValue?: ConcentrationValue\n allowedUnits?: ConcentrationUnit[]\n showConversion?: boolean\n molecularWeight?: number\n min?: number\n max?: number\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n placeholder?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n showConversion: true,\n disabled: false,\n error: false,\n size: 'md',\n placeholder: 'Enter value',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: ConcentrationValue | undefined]\n}>()\n\nconst { unitCategories, getConversionHint } = useConcentrationUnits()\n\n// Filter categories based on allowedUnits\nconst filteredCategories = computed(() => {\n if (!props.allowedUnits || props.allowedUnits.length === 0) {\n return unitCategories.value\n }\n return unitCategories.value\n .map(cat => ({\n label: cat.label,\n units: cat.units.filter(u => props.allowedUnits!.includes(u)),\n }))\n .filter(cat => cat.units.length > 0)\n})\n\n// Flatten all available units\nconst availableUnits = computed(() => {\n return filteredCategories.value.flatMap(cat => cat.units)\n})\n\n// Current value and unit\nconst currentValue = computed(() => props.modelValue?.value)\nconst currentUnit = computed(() => props.modelValue?.unit || availableUnits.value[0] || 'µM')\n\n// Conversion hint\nconst conversionHint = computed(() => {\n if (!props.showConversion || !props.modelValue) return null\n return getConversionHint(props.modelValue)\n})\n\nfunction handleValueInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = target.value === '' ? undefined : Number(target.value)\n\n if (value === undefined || isNaN(value)) {\n emit('update:modelValue', undefined)\n return\n }\n\n // Apply min/max constraints\n let clampedValue = value\n if (props.min !== undefined && clampedValue < props.min) {\n clampedValue = props.min\n }\n if (props.max !== undefined && clampedValue > props.max) {\n clampedValue = props.max\n }\n\n emit('update:modelValue', {\n value: clampedValue,\n unit: currentUnit.value,\n })\n}\n\nfunction handleUnitChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const unit = target.value as ConcentrationUnit\n\n emit('update:modelValue', {\n value: currentValue.value ?? 0,\n unit,\n })\n}\n\n// Ensure modelValue always has a valid unit\nwatch(availableUnits, (units) => {\n if (props.modelValue && !units.includes(props.modelValue.unit)) {\n emit('update:modelValue', {\n value: props.modelValue.value,\n unit: units[0] || 'µM',\n })\n }\n}, { immediate: true })\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-concentration-input',\n error ? 'mld-concentration-input--error' : '',\n disabled ? 'mld-concentration-input--disabled' : '',\n ]\"\n >\n <div :class=\"['mld-concentration-input__controls', `mld-concentration-input__controls--${size}`]\">\n <input\n type=\"number\"\n :value=\"currentValue\"\n :min=\"min\"\n :max=\"max\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n :class=\"[\n 'mld-concentration-input__value',\n `mld-concentration-input__value--${size}`,\n disabled ? 'mld-concentration-input__value--disabled' : '',\n ]\"\n aria-label=\"Concentration value\"\n @input=\"handleValueInput\"\n />\n\n <div class=\"mld-concentration-input__unit\">\n <select\n :value=\"currentUnit\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-concentration-input__unit-select',\n `mld-concentration-input__unit-select--${size}`,\n ]\"\n aria-label=\"Concentration unit\"\n @change=\"handleUnitChange\"\n >\n <template v-for=\"category in filteredCategories\" :key=\"category.label\">\n <optgroup\n v-if=\"filteredCategories.length > 1\"\n :label=\"category.label\"\n class=\"mld-concentration-input__unit-group\"\n >\n <option\n v-for=\"unit in category.units\"\n :key=\"unit\"\n :value=\"unit\"\n >\n {{ unit }}\n </option>\n </optgroup>\n <template v-else>\n <option\n v-for=\"unit in category.units\"\n :key=\"unit\"\n :value=\"unit\"\n >\n {{ unit }}\n </option>\n </template>\n </template>\n </select>\n </div>\n </div>\n\n <div\n v-if=\"showConversion && conversionHint\"\n class=\"mld-concentration-input__conversion\"\n >\n {{ conversionHint }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/concentration-input.css';\n</style>\n","import {\n useConcentrationUnits,\n type ConcentrationValue,\n type MolarityUnit,\n type MassVolumeUnit,\n} from './useConcentrationUnits'\n\n// Volume units\nexport type VolumeUnit = 'µL' | 'mL' | 'L'\n\nexport interface VolumeValue {\n value: number\n unit: VolumeUnit\n}\n\n// Dilution calculation types\nexport interface DilutionParams {\n stockConcentration: ConcentrationValue\n finalConcentration: ConcentrationValue\n finalVolume: VolumeValue\n}\n\nexport interface DilutionResult {\n stockVolume: VolumeValue\n diluentVolume: VolumeValue\n dilutionFactor: number\n valid: boolean\n error?: string\n}\n\n// Serial dilution types\nexport interface SerialDilutionParams {\n startingConcentration: ConcentrationValue\n dilutionFactor: number\n numberOfDilutions: number\n volumePerWell: VolumeValue\n}\n\nexport interface SerialDilutionStep {\n stepNumber: number\n concentration: ConcentrationValue\n transferVolume: VolumeValue\n diluentVolume: VolumeValue\n}\n\nexport interface SerialDilutionResult {\n steps: SerialDilutionStep[]\n totalStockVolume: VolumeValue\n valid: boolean\n error?: string\n}\n\n// Conversion types\nexport interface ConversionResult {\n result: ConcentrationValue\n valid: boolean\n error?: string\n}\n\n// Well concentration for plate integration\nexport interface WellConcentration {\n wellId: string\n concentration: ConcentrationValue\n volume?: VolumeValue\n}\n\nexport interface UseDoseCalculatorReturn {\n volumeUnits: VolumeUnit[]\n calculateDilution: (params: DilutionParams) => DilutionResult\n calculateSerialDilution: (params: SerialDilutionParams) => SerialDilutionResult\n convertMassToMolar: (\n mass: number,\n massUnit: MassVolumeUnit,\n mw: number\n ) => ConcentrationValue\n convertMolarToMass: (\n molar: number,\n molarUnit: MolarityUnit,\n mw: number\n ) => ConcentrationValue\n convertVolume: (value: number, from: VolumeUnit, to: VolumeUnit) => number\n formatVolume: (volume: VolumeValue, precision?: number) => string\n generateWellConcentrations: (\n result: SerialDilutionResult,\n wellIds: string[]\n ) => WellConcentration[]\n}\n\nconst VOLUME_UNITS: VolumeUnit[] = ['µL', 'mL', 'L']\n\nconst VOLUME_FACTORS: Record<VolumeUnit, number> = {\n 'µL': 1e-6,\n 'mL': 1e-3,\n 'L': 1,\n}\n\n/** Calculates single dilutions, serial dilution series, and plate-filling volumes for dose-response experiments. */\nexport function useDoseCalculator(): UseDoseCalculatorReturn {\n const { convert } = useConcentrationUnits()\n\n function convertVolume(value: number, from: VolumeUnit, to: VolumeUnit): number {\n if (from === to) return value\n const baseValue = value * VOLUME_FACTORS[from]\n return baseValue / VOLUME_FACTORS[to]\n }\n\n function formatVolume(volume: VolumeValue, precision: number = 3): string {\n const { value, unit } = volume\n if (value === 0) return `0 ${unit}`\n\n let formattedValue: string\n if (Math.abs(value) >= 1000) {\n formattedValue = value.toExponential(precision - 1)\n } else if (Math.abs(value) < 0.001) {\n formattedValue = value.toExponential(precision - 1)\n } else {\n formattedValue = value.toPrecision(precision)\n }\n\n // Remove trailing zeros\n formattedValue = formattedValue.replace(/\\.?0+$/, '')\n formattedValue = formattedValue.replace(/\\.?0+e/, 'e')\n\n return `${formattedValue} ${unit}`\n }\n\n function calculateDilution(params: DilutionParams): DilutionResult {\n const { stockConcentration, finalConcentration, finalVolume } = params\n\n // Validate inputs\n if (stockConcentration.value <= 0) {\n return {\n stockVolume: { value: 0, unit: 'µL' },\n diluentVolume: { value: 0, unit: 'µL' },\n dilutionFactor: 0,\n valid: false,\n error: 'Stock concentration must be positive',\n }\n }\n\n if (finalConcentration.value <= 0) {\n return {\n stockVolume: { value: 0, unit: 'µL' },\n diluentVolume: { value: 0, unit: 'µL' },\n dilutionFactor: 0,\n valid: false,\n error: 'Final concentration must be positive',\n }\n }\n\n if (finalVolume.value <= 0) {\n return {\n stockVolume: { value: 0, unit: 'µL' },\n diluentVolume: { value: 0, unit: 'µL' },\n dilutionFactor: 0,\n valid: false,\n error: 'Final volume must be positive',\n }\n }\n\n // Convert concentrations to same unit\n const convertedFinal = convert(\n finalConcentration.value,\n finalConcentration.unit,\n stockConcentration.unit\n )\n\n if (convertedFinal === null) {\n return {\n stockVolume: { value: 0, unit: 'µL' },\n diluentVolume: { value: 0, unit: 'µL' },\n dilutionFactor: 0,\n valid: false,\n error: 'Cannot convert between concentration units. Provide molecular weight for mass↔molarity conversion.',\n }\n }\n\n // Check that stock is more concentrated than final\n if (convertedFinal >= stockConcentration.value) {\n return {\n stockVolume: { value: 0, unit: 'µL' },\n diluentVolume: { value: 0, unit: 'µL' },\n dilutionFactor: 0,\n valid: false,\n error: 'Stock concentration must be higher than final concentration',\n }\n }\n\n // Calculate using C1V1 = C2V2\n const dilutionFactor = stockConcentration.value / convertedFinal\n const finalVolumeInL = convertVolume(finalVolume.value, finalVolume.unit, 'L')\n const stockVolumeInL = finalVolumeInL / dilutionFactor\n const diluentVolumeInL = finalVolumeInL - stockVolumeInL\n\n // Convert to appropriate unit (µL for small volumes, mL for larger)\n let outputUnit: VolumeUnit = 'µL'\n if (stockVolumeInL >= 0.001) outputUnit = 'mL'\n if (stockVolumeInL >= 1) outputUnit = 'L'\n\n return {\n stockVolume: {\n value: convertVolume(stockVolumeInL, 'L', outputUnit),\n unit: outputUnit,\n },\n diluentVolume: {\n value: convertVolume(diluentVolumeInL, 'L', outputUnit),\n unit: outputUnit,\n },\n dilutionFactor,\n valid: true,\n }\n }\n\n function calculateSerialDilution(params: SerialDilutionParams): SerialDilutionResult {\n const { startingConcentration, dilutionFactor, numberOfDilutions, volumePerWell } = params\n\n // Validate inputs\n if (startingConcentration.value <= 0) {\n return {\n steps: [],\n totalStockVolume: { value: 0, unit: 'µL' },\n valid: false,\n error: 'Starting concentration must be positive',\n }\n }\n\n if (dilutionFactor <= 1) {\n return {\n steps: [],\n totalStockVolume: { value: 0, unit: 'µL' },\n valid: false,\n error: 'Dilution factor must be greater than 1',\n }\n }\n\n if (numberOfDilutions < 1) {\n return {\n steps: [],\n totalStockVolume: { value: 0, unit: 'µL' },\n valid: false,\n error: 'Number of dilutions must be at least 1',\n }\n }\n\n if (volumePerWell.value <= 0) {\n return {\n steps: [],\n totalStockVolume: { value: 0, unit: 'µL' },\n valid: false,\n error: 'Volume per well must be positive',\n }\n }\n\n const steps: SerialDilutionStep[] = []\n let currentConcentration = startingConcentration.value\n const transferVolume = volumePerWell.value / (dilutionFactor - 1)\n const diluentVolume = volumePerWell.value - transferVolume\n\n for (let i = 0; i < numberOfDilutions; i++) {\n steps.push({\n stepNumber: i + 1,\n concentration: {\n value: currentConcentration,\n unit: startingConcentration.unit,\n },\n transferVolume: {\n value: i === 0 ? volumePerWell.value : transferVolume,\n unit: volumePerWell.unit,\n },\n diluentVolume: {\n value: i === 0 ? 0 : diluentVolume,\n unit: volumePerWell.unit,\n },\n })\n currentConcentration /= dilutionFactor\n }\n\n // Total stock needed for first well\n const totalStockVolume: VolumeValue = {\n value: volumePerWell.value + (transferVolume * (numberOfDilutions - 1)),\n unit: volumePerWell.unit,\n }\n\n return {\n steps,\n totalStockVolume,\n valid: true,\n }\n }\n\n function convertMassToMolar(\n mass: number,\n massUnit: MassVolumeUnit,\n mw: number\n ): ConcentrationValue {\n // Convert mass to g/mL, then to M using MW\n const converted = convert(mass, massUnit, 'µM', mw)\n if (converted !== null) {\n return { value: converted, unit: 'µM' }\n }\n // Fallback to manual calculation\n // mass (g/mL) / MW (g/mol) = mol/L = M\n const massInGML = mass * getMassVolumeFactor(massUnit)\n const molarConc = massInGML / mw\n return { value: molarConc * 1e6, unit: 'µM' }\n }\n\n function convertMolarToMass(\n molar: number,\n molarUnit: MolarityUnit,\n mw: number\n ): ConcentrationValue {\n // Convert M to mass using MW\n const converted = convert(molar, molarUnit, 'µg/mL', mw)\n if (converted !== null) {\n return { value: converted, unit: 'µg/mL' }\n }\n // Fallback to manual calculation\n // mol/L * MW (g/mol) = g/L = mg/mL\n const molarInM = molar * getMolarityFactor(molarUnit)\n const massConc = molarInM * mw * 1e6 // µg/mL\n return { value: massConc, unit: 'µg/mL' }\n }\n\n function getMassVolumeFactor(unit: MassVolumeUnit): number {\n const factors: Record<MassVolumeUnit, number> = {\n 'pg/mL': 1e-12,\n 'ng/mL': 1e-9,\n 'µg/mL': 1e-6,\n 'mg/mL': 1e-3,\n 'g/mL': 1,\n }\n return factors[unit]\n }\n\n function getMolarityFactor(unit: MolarityUnit): number {\n const factors: Record<MolarityUnit, number> = {\n 'pM': 1e-12,\n 'nM': 1e-9,\n 'µM': 1e-6,\n 'mM': 1e-3,\n 'M': 1,\n }\n return factors[unit]\n }\n\n function generateWellConcentrations(\n result: SerialDilutionResult,\n wellIds: string[]\n ): WellConcentration[] {\n if (!result.valid || result.steps.length === 0) return []\n\n return result.steps.slice(0, wellIds.length).map((step, index) => ({\n wellId: wellIds[index],\n concentration: step.concentration,\n volume: step.transferVolume,\n }))\n }\n\n return {\n volumeUnits: VOLUME_UNITS,\n calculateDilution,\n calculateSerialDilution,\n convertMassToMolar,\n convertMolarToMass,\n convertVolume,\n formatVolume,\n generateWellConcentrations,\n }\n}\n","/**\n * Composable for generating dilution series used in plate-based experiments.\n * Provides geometric series generation from presets (2x, 3x, ½ log, 10x)\n * and utility functions for series manipulation.\n */\n\nexport interface LevelEntry {\n value: number\n replicates: number\n}\n\nexport interface DilutionPreset {\n label: string\n factor: number | 'half-log'\n}\n\nexport const DEFAULT_PRESETS: DilutionPreset[] = [\n { label: '2x', factor: 2 },\n { label: '3x', factor: 3 },\n { label: '½ log', factor: 'half-log' },\n { label: '10x', factor: 10 },\n]\n\nexport const DEFAULT_UNITS = ['µM', 'nM', 'mM', 'pM']\n\nfunction resolveFactor(factor: number | 'half-log'): number {\n return factor === 'half-log' ? Math.sqrt(10) : factor\n}\n\nfunction roundToSignificant(value: number, digits: number = 4): number {\n if (value === 0) return 0\n const magnitude = Math.floor(Math.log10(Math.abs(value))) + 1\n const factor = Math.pow(10, digits - magnitude)\n return Math.round(value * factor) / factor\n}\n\nexport function generateDilutionSeries(\n startConcentration: number,\n count: number,\n factor: number | 'half-log',\n defaultReplicates: number = 1,\n): LevelEntry[] {\n if (count < 1 || startConcentration <= 0) return []\n\n const divisor = resolveFactor(factor)\n const levels: LevelEntry[] = []\n\n for (let i = 0; i < count; i++) {\n levels.push({\n value: roundToSignificant(startConcentration / Math.pow(divisor, i)),\n replicates: defaultReplicates,\n })\n }\n\n return levels\n}\n\nexport interface UseReagentSeriesReturn {\n generateSeries: (\n start: number,\n count: number,\n factor: number | 'half-log',\n defaultReplicates?: number,\n ) => LevelEntry[]\n sortLevels: (levels: LevelEntry[], order?: 'asc' | 'desc') => LevelEntry[]\n totalPositions: (levels: LevelEntry[]) => number\n}\n\n/** Generates and sorts geometric dilution series (2x, 3x, ½ log, 10x) for plate-based experiments. */\nexport function useReagentSeries(): UseReagentSeriesReturn {\n function generateSeries(\n start: number,\n count: number,\n factor: number | 'half-log',\n defaultReplicates: number = 1,\n ): LevelEntry[] {\n return generateDilutionSeries(start, count, factor, defaultReplicates)\n }\n\n function sortLevels(levels: LevelEntry[], order: 'asc' | 'desc' = 'desc'): LevelEntry[] {\n return [...levels].sort((a, b) =>\n order === 'desc' ? b.value - a.value : a.value - b.value,\n )\n }\n\n function totalPositions(levels: LevelEntry[]): number {\n return levels.reduce((sum, l) => sum + l.replicates, 0)\n }\n\n return { generateSeries, sortLevels, totalPositions }\n}\n","import { ref, computed, type Ref, type ComputedRef } from 'vue'\nimport type { ProtocolStep, ProtocolStepType, ProtocolStepStatus } from '../types'\n\n// Parameter definition for step templates\nexport interface ParameterDefinition {\n key: string\n label: string\n type: 'number' | 'text' | 'select' | 'concentration' | 'temperature' | 'duration' | 'reagent'\n unit?: string\n options?: Array<{ value: string; label: string }>\n required?: boolean\n default?: unknown\n min?: number\n max?: number\n placeholder?: string\n}\n\n// Step template definition\nexport interface StepTemplate {\n id: string\n type: ProtocolStepType\n name: string\n description?: string\n defaultDuration?: number\n parameters: ParameterDefinition[]\n isBuiltIn?: boolean\n}\n\n// Validation result\nexport interface ValidationResult {\n valid: boolean\n errors: Record<string, string>\n}\n\nexport interface UseProtocolTemplatesReturn {\n builtInTemplates: StepTemplate[]\n customTemplates: Ref<StepTemplate[]>\n allTemplates: ComputedRef<StepTemplate[]>\n getTemplateByType: (type: ProtocolStepType) => StepTemplate | undefined\n getTemplateById: (id: string) => StepTemplate | undefined\n saveCustomTemplate: (template: StepTemplate) => void\n deleteCustomTemplate: (templateId: string) => void\n validateStep: (step: ProtocolStep, template: StepTemplate) => ValidationResult\n createStepFromTemplate: (template: StepTemplate, overrides?: Partial<ProtocolStep>) => ProtocolStep\n formatParameterValue: (value: unknown, param: ParameterDefinition) => string\n}\n\n// Built-in templates\nconst BUILT_IN_TEMPLATES: StepTemplate[] = [\n {\n id: 'builtin-incubation',\n type: 'incubation',\n name: 'Incubation',\n description: 'Incubate samples at specified conditions',\n defaultDuration: 60,\n isBuiltIn: true,\n parameters: [\n {\n key: 'temperature',\n label: 'Temperature',\n type: 'temperature',\n unit: '°C',\n required: true,\n default: 37,\n min: -80,\n max: 100,\n },\n {\n key: 'duration',\n label: 'Duration',\n type: 'duration',\n unit: 'hours',\n required: true,\n default: 24,\n min: 0,\n },\n {\n key: 'co2',\n label: 'CO2',\n type: 'number',\n unit: '%',\n required: false,\n default: 5,\n min: 0,\n max: 100,\n },\n {\n key: 'humidity',\n label: 'Humidity',\n type: 'number',\n unit: '%',\n required: false,\n default: 95,\n min: 0,\n max: 100,\n },\n ],\n },\n {\n id: 'builtin-wash',\n type: 'wash',\n name: 'Wash',\n description: 'Wash samples with buffer',\n defaultDuration: 5,\n isBuiltIn: true,\n parameters: [\n {\n key: 'buffer',\n label: 'Buffer',\n type: 'select',\n required: true,\n default: 'PBS',\n options: [\n { value: 'PBS', label: 'PBS' },\n { value: 'PBST', label: 'PBST' },\n { value: 'TBS', label: 'TBS' },\n { value: 'TBST', label: 'TBST' },\n { value: 'Water', label: 'Water' },\n { value: 'Other', label: 'Other' },\n ],\n },\n {\n key: 'volume',\n label: 'Volume',\n type: 'number',\n unit: 'µL',\n required: true,\n default: 200,\n min: 0,\n },\n {\n key: 'cycles',\n label: 'Wash Cycles',\n type: 'number',\n required: true,\n default: 3,\n min: 1,\n max: 10,\n },\n ],\n },\n {\n id: 'builtin-addition',\n type: 'addition',\n name: 'Addition',\n description: 'Add reagent or compound to samples',\n defaultDuration: 5,\n isBuiltIn: true,\n parameters: [\n {\n key: 'reagent',\n label: 'Reagent',\n type: 'text',\n required: true,\n placeholder: 'Enter reagent name',\n },\n {\n key: 'volume',\n label: 'Volume',\n type: 'number',\n unit: 'µL',\n required: true,\n default: 100,\n min: 0,\n },\n {\n key: 'concentration',\n label: 'Concentration',\n type: 'concentration',\n required: false,\n },\n ],\n },\n {\n id: 'builtin-measurement',\n type: 'measurement',\n name: 'Measurement',\n description: 'Take measurements using specified instrument',\n defaultDuration: 15,\n isBuiltIn: true,\n parameters: [\n {\n key: 'instrument',\n label: 'Instrument',\n type: 'select',\n required: true,\n options: [\n { value: 'plate_reader', label: 'Plate Reader' },\n { value: 'microscope', label: 'Microscope' },\n { value: 'flow_cytometer', label: 'Flow Cytometer' },\n { value: 'spectrophotometer', label: 'Spectrophotometer' },\n { value: 'other', label: 'Other' },\n ],\n },\n {\n key: 'readout',\n label: 'Readout Type',\n type: 'text',\n required: false,\n placeholder: 'e.g., Absorbance 450nm',\n },\n {\n key: 'notes',\n label: 'Parameters',\n type: 'text',\n required: false,\n placeholder: 'Additional parameters',\n },\n ],\n },\n {\n id: 'builtin-centrifuge',\n type: 'centrifuge',\n name: 'Centrifuge',\n description: 'Centrifuge samples',\n defaultDuration: 10,\n isBuiltIn: true,\n parameters: [\n {\n key: 'speed',\n label: 'Speed',\n type: 'number',\n unit: 'RPM',\n required: true,\n default: 1000,\n min: 100,\n max: 20000,\n },\n {\n key: 'duration',\n label: 'Duration',\n type: 'number',\n unit: 'min',\n required: true,\n default: 5,\n min: 1,\n },\n {\n key: 'temperature',\n label: 'Temperature',\n type: 'temperature',\n unit: '°C',\n required: false,\n default: 4,\n min: -10,\n max: 40,\n },\n ],\n },\n {\n id: 'builtin-transfer',\n type: 'transfer',\n name: 'Transfer',\n description: 'Transfer samples between containers',\n defaultDuration: 10,\n isBuiltIn: true,\n parameters: [\n {\n key: 'source',\n label: 'Source',\n type: 'text',\n required: true,\n placeholder: 'Source container',\n },\n {\n key: 'destination',\n label: 'Destination',\n type: 'text',\n required: true,\n placeholder: 'Destination container',\n },\n {\n key: 'volume',\n label: 'Volume',\n type: 'number',\n unit: 'µL',\n required: true,\n default: 100,\n min: 0,\n },\n ],\n },\n {\n id: 'builtin-mix',\n type: 'mix',\n name: 'Mix',\n description: 'Mix samples',\n defaultDuration: 5,\n isBuiltIn: true,\n parameters: [\n {\n key: 'method',\n label: 'Method',\n type: 'select',\n required: true,\n default: 'pipette',\n options: [\n { value: 'pipette', label: 'Pipette mixing' },\n { value: 'vortex', label: 'Vortex' },\n { value: 'shaker', label: 'Orbital shaker' },\n { value: 'inversion', label: 'Inversion' },\n ],\n },\n {\n key: 'duration',\n label: 'Duration',\n type: 'number',\n unit: 'sec',\n required: false,\n default: 30,\n min: 1,\n },\n {\n key: 'speed',\n label: 'Speed/Intensity',\n type: 'text',\n required: false,\n placeholder: 'e.g., 300 RPM',\n },\n ],\n },\n {\n id: 'builtin-custom',\n type: 'custom',\n name: 'Custom Step',\n description: 'Custom protocol step',\n defaultDuration: 15,\n isBuiltIn: true,\n parameters: [\n {\n key: 'description',\n label: 'Description',\n type: 'text',\n required: true,\n placeholder: 'Describe the step',\n },\n {\n key: 'notes',\n label: 'Notes',\n type: 'text',\n required: false,\n placeholder: 'Additional notes',\n },\n ],\n },\n]\n\n// Storage key for custom templates\nconst STORAGE_KEY = 'mld-custom-protocol-templates'\n\nfunction loadCustomTemplates(): StepTemplate[] {\n try {\n const stored = localStorage.getItem(STORAGE_KEY)\n if (stored) {\n return JSON.parse(stored)\n }\n } catch {\n // Ignore errors\n }\n return []\n}\n\nfunction saveCustomTemplatesToStorage(templates: StepTemplate[]) {\n try {\n localStorage.setItem(STORAGE_KEY, JSON.stringify(templates))\n } catch {\n // Ignore errors\n }\n}\n\n/** Provides built-in and custom protocol step templates with validation and step creation helpers. */\nexport function useProtocolTemplates(): UseProtocolTemplatesReturn {\n const customTemplates = ref<StepTemplate[]>(loadCustomTemplates())\n\n const allTemplates = computed(() => {\n return [...BUILT_IN_TEMPLATES, ...customTemplates.value]\n })\n\n function getTemplateByType(type: ProtocolStepType): StepTemplate | undefined {\n // First check custom templates, then built-in\n return (\n customTemplates.value.find((t) => t.type === type) ||\n BUILT_IN_TEMPLATES.find((t) => t.type === type)\n )\n }\n\n function getTemplateById(id: string): StepTemplate | undefined {\n return (\n customTemplates.value.find((t) => t.id === id) ||\n BUILT_IN_TEMPLATES.find((t) => t.id === id)\n )\n }\n\n function saveCustomTemplate(template: StepTemplate) {\n const newTemplate = {\n ...template,\n isBuiltIn: false,\n id: template.id || `custom-${Date.now()}`,\n }\n\n const index = customTemplates.value.findIndex((t) => t.id === newTemplate.id)\n if (index >= 0) {\n customTemplates.value[index] = newTemplate\n } else {\n customTemplates.value.push(newTemplate)\n }\n\n saveCustomTemplatesToStorage(customTemplates.value)\n }\n\n function deleteCustomTemplate(templateId: string) {\n const index = customTemplates.value.findIndex((t) => t.id === templateId)\n if (index >= 0) {\n customTemplates.value.splice(index, 1)\n saveCustomTemplatesToStorage(customTemplates.value)\n }\n }\n\n function validateStep(step: ProtocolStep, template: StepTemplate): ValidationResult {\n const errors: Record<string, string> = {}\n\n if (!step.name || step.name.trim() === '') {\n errors.name = 'Step name is required'\n }\n\n for (const param of template.parameters) {\n if (param.required) {\n const value = step.parameters?.[param.key]\n if (value === undefined || value === null || value === '') {\n errors[param.key] = `${param.label} is required`\n }\n }\n\n if (param.type === 'number' || param.type === 'temperature' || param.type === 'duration') {\n const value = step.parameters?.[param.key]\n if (value !== undefined && value !== null && value !== '') {\n const numValue = Number(value)\n if (isNaN(numValue)) {\n errors[param.key] = `${param.label} must be a number`\n } else {\n if (param.min !== undefined && numValue < param.min) {\n errors[param.key] = `${param.label} must be at least ${param.min}`\n }\n if (param.max !== undefined && numValue > param.max) {\n errors[param.key] = `${param.label} must be at most ${param.max}`\n }\n }\n }\n }\n }\n\n return {\n valid: Object.keys(errors).length === 0,\n errors,\n }\n }\n\n function createStepFromTemplate(\n template: StepTemplate,\n overrides?: Partial<ProtocolStep>\n ): ProtocolStep {\n // Build default parameters from template\n const parameters: Record<string, unknown> = {}\n for (const param of template.parameters) {\n if (param.default !== undefined) {\n parameters[param.key] = param.default\n }\n }\n\n return {\n id: `step-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,\n type: template.type,\n name: template.name,\n description: template.description,\n duration: template.defaultDuration,\n status: 'pending' as ProtocolStepStatus,\n parameters,\n order: 0,\n ...overrides,\n }\n }\n\n function formatParameterValue(value: unknown, param: ParameterDefinition): string {\n if (value === undefined || value === null || value === '') {\n return '-'\n }\n\n if (param.type === 'select' && param.options) {\n const option = param.options.find((o) => o.value === value)\n if (option) return option.label\n }\n\n if (param.type === 'concentration' && typeof value === 'object') {\n const conc = value as { value: number; unit: string }\n return `${conc.value} ${conc.unit}`\n }\n\n const strValue = String(value)\n if (param.unit) {\n return `${strValue} ${param.unit}`\n }\n\n return strValue\n }\n\n return {\n builtInTemplates: BUILT_IN_TEMPLATES,\n customTemplates,\n allTemplates,\n getTemplateByType,\n getTemplateById,\n saveCustomTemplate,\n deleteCustomTemplate,\n validateStep,\n createStepFromTemplate,\n formatParameterValue,\n }\n}\n","// Types\nexport interface ParsedElement {\n symbol: string\n count: number\n}\n\nexport interface FormulaParseResult {\n elements: Record<string, number>\n valid: boolean\n error?: string\n}\n\nexport type FormulaPartType = 'element' | 'subscript' | 'superscript' | 'paren' | 'dot' | 'charge'\n\nexport interface FormulaPart {\n type: FormulaPartType\n text: string\n}\n\n// ~30 common elements with atomic weights\nexport const ATOMIC_WEIGHTS: Record<string, number> = {\n H: 1.008, He: 4.003, Li: 6.941, Be: 9.012, B: 10.81,\n C: 12.011, N: 14.007, O: 15.999, F: 18.998, Ne: 20.180,\n Na: 22.990, Mg: 24.305, Al: 26.982, Si: 28.086, P: 30.974,\n S: 32.065, Cl: 35.453, Ar: 39.948, K: 39.098, Ca: 40.078,\n Mn: 54.938, Fe: 55.845, Co: 58.933, Ni: 58.693, Cu: 63.546,\n Zn: 65.38, Br: 79.904, Ag: 107.868, I: 126.904, Au: 196.967,\n}\n\nfunction parseFormulaSegment(formula: string): FormulaParseResult {\n const elements: Record<string, number> = {}\n const stack: Record<string, number>[] = [{}]\n let i = 0\n\n while (i < formula.length) {\n const ch = formula[i]\n\n if (ch === '(') {\n stack.push({})\n i++\n } else if (ch === ')') {\n if (stack.length < 2) {\n return { elements: {}, valid: false, error: 'Unmatched closing parenthesis' }\n }\n i++\n // Read multiplier after closing paren\n let numStr = ''\n while (i < formula.length && /\\d/.test(formula[i])) {\n numStr += formula[i]\n i++\n }\n const multiplier = numStr ? parseInt(numStr, 10) : 1\n const top = stack.pop()!\n const current = stack[stack.length - 1]\n for (const [el, count] of Object.entries(top)) {\n current[el] = (current[el] || 0) + count * multiplier\n }\n } else if (/[A-Z]/.test(ch)) {\n // Element symbol: uppercase letter optionally followed by lowercase\n let symbol = ch\n i++\n while (i < formula.length && /[a-z]/.test(formula[i])) {\n symbol += formula[i]\n i++\n }\n // Validate element\n if (!(symbol in ATOMIC_WEIGHTS)) {\n return { elements: {}, valid: false, error: `Unknown element: ${symbol}` }\n }\n // Read count\n let numStr = ''\n while (i < formula.length && /\\d/.test(formula[i])) {\n numStr += formula[i]\n i++\n }\n const count = numStr ? parseInt(numStr, 10) : 1\n const current = stack[stack.length - 1]\n current[symbol] = (current[symbol] || 0) + count\n } else {\n return { elements: {}, valid: false, error: `Unexpected character: ${ch}` }\n }\n }\n\n if (stack.length !== 1) {\n return { elements: {}, valid: false, error: 'Unmatched opening parenthesis' }\n }\n\n Object.assign(elements, stack[0])\n return { elements, valid: true }\n}\n\n/** Parses and renders chemical formulas, computes molecular weight, and generates rich-text formula parts. */\nexport function useChemicalFormula() {\n function parseFormula(formula: string): FormulaParseResult {\n const trimmed = formula.trim()\n if (!trimmed) {\n return { elements: {}, valid: false, error: 'Empty formula' }\n }\n\n // Split on hydrate separator (· or .)\n const parts = trimmed.split(/[·.]/)\n const combined: Record<string, number> = {}\n\n for (const part of parts) {\n const segment = part.trim()\n if (!segment) continue\n\n // Check for leading coefficient (e.g., 5H2O)\n let coefficient = 1\n let formulaStr = segment\n const coeffMatch = segment.match(/^(\\d+)([A-Z].*)$/)\n if (coeffMatch) {\n coefficient = parseInt(coeffMatch[1], 10)\n formulaStr = coeffMatch[2]\n }\n\n const result = parseFormulaSegment(formulaStr)\n if (!result.valid) {\n return result\n }\n\n for (const [el, count] of Object.entries(result.elements)) {\n combined[el] = (combined[el] || 0) + count * coefficient\n }\n }\n\n return { elements: combined, valid: true }\n }\n\n function calculateMW(elements: Record<string, number>): number {\n let mw = 0\n for (const [el, count] of Object.entries(elements)) {\n const weight = ATOMIC_WEIGHTS[el]\n if (weight !== undefined) {\n mw += weight * count\n }\n }\n return Math.round(mw * 1000) / 1000\n }\n\n function renderFormulaParts(formula: string): FormulaPart[] {\n const parts: FormulaPart[] = []\n const trimmed = formula.trim()\n let i = 0\n\n while (i < trimmed.length) {\n const ch = trimmed[i]\n\n // Hydrate dot separator\n if (ch === '·' || (ch === '.' && i > 0 && /\\d/.test(trimmed[i - 1]) && i + 1 < trimmed.length && /\\d/.test(trimmed[i + 1]))) {\n // Disambiguate: '.' as hydrate separator only if followed by a digit then uppercase\n // For simplicity, treat '·' always as dot\n if (ch === '·') {\n parts.push({ type: 'dot', text: '·' })\n i++\n continue\n }\n }\n\n // Parentheses\n if (ch === '(' || ch === ')') {\n parts.push({ type: 'paren', text: ch })\n i++\n // After ')' check for subscript number\n if (ch === ')' && i < trimmed.length && /\\d/.test(trimmed[i])) {\n let numStr = ''\n while (i < trimmed.length && /\\d/.test(trimmed[i])) {\n numStr += trimmed[i]\n i++\n }\n parts.push({ type: 'subscript', text: numStr })\n }\n continue\n }\n\n // Charge notation: ^2+, ^2-, 2+, 2- at end, or +, -\n if (ch === '^') {\n i++\n let chargeStr = ''\n while (i < trimmed.length && /[\\d+\\-]/.test(trimmed[i])) {\n chargeStr += trimmed[i]\n i++\n }\n if (chargeStr) {\n parts.push({ type: 'charge', text: chargeStr })\n }\n continue\n }\n\n // Element symbol: uppercase followed by optional lowercase\n if (/[A-Z]/.test(ch)) {\n let symbol = ch\n i++\n while (i < trimmed.length && /[a-z]/.test(trimmed[i])) {\n symbol += trimmed[i]\n i++\n }\n parts.push({ type: 'element', text: symbol })\n\n // Check for subscript number after element\n if (i < trimmed.length && /\\d/.test(trimmed[i])) {\n let numStr = ''\n while (i < trimmed.length && /\\d/.test(trimmed[i])) {\n numStr += trimmed[i]\n i++\n }\n parts.push({ type: 'subscript', text: numStr })\n\n // Check for charge after subscript (e.g., Fe2+ or SO4^2-)\n if (i < trimmed.length && (trimmed[i] === '+' || trimmed[i] === '-')) {\n parts.push({ type: 'charge', text: numStr + trimmed[i] })\n // Remove the subscript we just added since it's actually part of charge\n // Actually, for cases like Fe2+, the 2+ is the charge, not a subscript\n // But for H2O the 2 is a subscript. We need context.\n // Simplification: if followed by +/-, treat trailing digits as part of charge\n parts.splice(parts.length - 2, 1) // Remove the subscript\n i++\n continue\n }\n }\n continue\n }\n\n // Leading digit (hydrate coefficient like 5H2O)\n if (/\\d/.test(ch)) {\n let numStr = ''\n while (i < trimmed.length && /\\d/.test(trimmed[i])) {\n numStr += trimmed[i]\n i++\n }\n // Could be a subscript or coefficient depending on context\n // If next char is uppercase, it's a coefficient (render as element-like text)\n // If at end or followed by +/-, it's a charge component\n if (i < trimmed.length && (trimmed[i] === '+' || trimmed[i] === '-')) {\n parts.push({ type: 'charge', text: numStr + trimmed[i] })\n i++\n } else {\n parts.push({ type: 'subscript', text: numStr })\n }\n continue\n }\n\n // Standalone + or - (charge)\n if (ch === '+' || ch === '-') {\n parts.push({ type: 'charge', text: ch })\n i++\n continue\n }\n\n // Dot as hydrate separator\n if (ch === '.') {\n parts.push({ type: 'dot', text: '·' })\n i++\n continue\n }\n\n // Skip whitespace\n if (/\\s/.test(ch)) {\n i++\n continue\n }\n\n // Unknown character, skip\n i++\n }\n\n return parts\n }\n\n return {\n parseFormula,\n calculateMW,\n renderFormulaParts,\n }\n}\n","<script setup lang=\"ts\">\n/** Text input for chemical formulas (e.g. Ca(OH)₂) with live rendered preview and calculated molecular weight display. */\nimport { useChemicalFormula, type FormulaPart } from '../composables/useChemicalFormula'\n\ninterface Props {\n modelValue?: string\n showPreview?: boolean\n showMW?: boolean\n placeholder?: string\n error?: boolean\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n modelValue: '',\n showPreview: true,\n showMW: true,\n placeholder: 'e.g. Ca(OH)2',\n error: false,\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'mw': [mw: number | null]\n}>()\n\nconst { parseFormula, calculateMW, renderFormulaParts } = useChemicalFormula()\n\nfunction getParseResult(value: string) {\n if (!value) return null\n return parseFormula(value)\n}\n\nfunction getMW(value: string): number | null {\n const result = getParseResult(value)\n if (!result || !result.valid) return null\n return calculateMW(result.elements)\n}\n\nfunction getParts(value: string): FormulaPart[] {\n if (!value) return []\n return renderFormulaParts(value)\n}\n\nfunction getError(value: string): string | null {\n if (!value) return null\n const result = getParseResult(value)\n if (!result) return null\n if (!result.valid) return result.error || 'Invalid formula'\n return null\n}\n\nfunction formatMW(value: string): string {\n const mw = getMW(value)\n if (mw === null) return ''\n return `${mw.toFixed(2)} g/mol`\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.value)\n emit('mw', getMW(target.value))\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-formula-input',\n error ? 'mld-formula-input--error' : '',\n disabled ? 'mld-formula-input--disabled' : '',\n ]\"\n >\n <div class=\"mld-formula-input__field\">\n <input\n type=\"text\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-formula-input__input',\n `mld-formula-input__input--${size}`,\n ]\"\n aria-label=\"Chemical formula\"\n @input=\"handleInput\"\n />\n\n <div\n v-if=\"(showPreview && modelValue) || (showMW && modelValue && !getError(modelValue) && getMW(modelValue) !== null)\"\n class=\"mld-formula-input__preview\"\n >\n <span v-if=\"showPreview && modelValue\" class=\"mld-formula-input__preview-formula\">\n <template v-for=\"(part, i) in getParts(modelValue)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" style=\"vertical-align: sub; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" style=\"vertical-align: super; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" style=\"color: var(--text-secondary);\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" style=\"margin: 0 0.125em; color: var(--text-muted);\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" style=\"vertical-align: super; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n </template>\n </span>\n <span\n v-if=\"showMW && modelValue && !getError(modelValue) && getMW(modelValue) !== null\"\n class=\"mld-formula-input__mw\"\n >\n {{ formatMW(modelValue) }}\n </span>\n </div>\n </div>\n\n <div\n v-if=\"modelValue && getError(modelValue)\"\n class=\"mld-formula-input__error-text\"\n >\n {{ getError(modelValue) }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/formula-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Text input for chemical formulas (e.g. Ca(OH)₂) with live rendered preview and calculated molecular weight display. */\nimport { useChemicalFormula, type FormulaPart } from '../composables/useChemicalFormula'\n\ninterface Props {\n modelValue?: string\n showPreview?: boolean\n showMW?: boolean\n placeholder?: string\n error?: boolean\n disabled?: boolean\n size?: 'sm' | 'md' | 'lg'\n}\n\nwithDefaults(defineProps<Props>(), {\n modelValue: '',\n showPreview: true,\n showMW: true,\n placeholder: 'e.g. Ca(OH)2',\n error: false,\n disabled: false,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n 'mw': [mw: number | null]\n}>()\n\nconst { parseFormula, calculateMW, renderFormulaParts } = useChemicalFormula()\n\nfunction getParseResult(value: string) {\n if (!value) return null\n return parseFormula(value)\n}\n\nfunction getMW(value: string): number | null {\n const result = getParseResult(value)\n if (!result || !result.valid) return null\n return calculateMW(result.elements)\n}\n\nfunction getParts(value: string): FormulaPart[] {\n if (!value) return []\n return renderFormulaParts(value)\n}\n\nfunction getError(value: string): string | null {\n if (!value) return null\n const result = getParseResult(value)\n if (!result) return null\n if (!result.valid) return result.error || 'Invalid formula'\n return null\n}\n\nfunction formatMW(value: string): string {\n const mw = getMW(value)\n if (mw === null) return ''\n return `${mw.toFixed(2)} g/mol`\n}\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLInputElement\n emit('update:modelValue', target.value)\n emit('mw', getMW(target.value))\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-formula-input',\n error ? 'mld-formula-input--error' : '',\n disabled ? 'mld-formula-input--disabled' : '',\n ]\"\n >\n <div class=\"mld-formula-input__field\">\n <input\n type=\"text\"\n :value=\"modelValue\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-formula-input__input',\n `mld-formula-input__input--${size}`,\n ]\"\n aria-label=\"Chemical formula\"\n @input=\"handleInput\"\n />\n\n <div\n v-if=\"(showPreview && modelValue) || (showMW && modelValue && !getError(modelValue) && getMW(modelValue) !== null)\"\n class=\"mld-formula-input__preview\"\n >\n <span v-if=\"showPreview && modelValue\" class=\"mld-formula-input__preview-formula\">\n <template v-for=\"(part, i) in getParts(modelValue)\" :key=\"i\">\n <span v-if=\"part.type === 'element'\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'subscript'\" style=\"vertical-align: sub; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'superscript'\" style=\"vertical-align: super; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'paren'\" style=\"color: var(--text-secondary);\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'dot'\" style=\"margin: 0 0.125em; color: var(--text-muted);\">{{ part.text }}</span>\n <span v-else-if=\"part.type === 'charge'\" style=\"vertical-align: super; font-size: 0.75em; line-height: 0;\">{{ part.text }}</span>\n </template>\n </span>\n <span\n v-if=\"showMW && modelValue && !getError(modelValue) && getMW(modelValue) !== null\"\n class=\"mld-formula-input__mw\"\n >\n {{ formatMW(modelValue) }}\n </span>\n </div>\n </div>\n\n <div\n v-if=\"modelValue && getError(modelValue)\"\n class=\"mld-formula-input__error-text\"\n >\n {{ getError(modelValue) }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/formula-input.css';\n</style>\n","export type SequenceType = 'dna' | 'rna' | 'protein' | 'auto'\n\nexport interface SequenceStats {\n length: number\n gcPercent?: number\n molecularWeight?: number\n}\n\nconst DNA_CHARS = /[^ATCGNatcgn]/g\nconst RNA_CHARS = /[^AUCGNaucgn]/g\nconst PROTEIN_CHARS = /[^ACDEFGHIKLMNPQRSTVWYacdefghiklmnpqrstvwy]/g\n\nconst DNA_COMPLEMENT: Record<string, string> = {\n A: 'T', T: 'A', C: 'G', G: 'C', N: 'N',\n a: 't', t: 'a', c: 'g', g: 'c', n: 'n',\n}\n\nconst RNA_COMPLEMENT: Record<string, string> = {\n A: 'U', U: 'A', C: 'G', G: 'C', N: 'N',\n a: 'u', u: 'a', c: 'g', g: 'c', n: 'n',\n}\n\n// Average molecular weights per residue\nconst MW_DNA_NT = 330 // Da per nucleotide (average)\nconst MW_RNA_NT = 340 // Da per nucleotide (average)\nconst MW_PROTEIN_AA = 110 // Da per amino acid (average)\n\n/** Detects, validates, cleans, and computes stats (GC%, MW) for DNA, RNA, and protein sequences. */\nexport function useSequenceUtils() {\n function detectSequenceType(seq: string): 'dna' | 'rna' | 'protein' {\n const upper = seq.toUpperCase()\n\n // If contains U but not T, it's RNA\n if (upper.includes('U') && !upper.includes('T')) {\n return 'rna'\n }\n\n // If only contains ATCGN characters, it's DNA\n if (/^[ATCGN]+$/i.test(seq)) {\n return 'dna'\n }\n\n return 'protein'\n }\n\n function validateSequence(seq: string, type: SequenceType): string {\n const effectiveType = type === 'auto' ? detectSequenceType(seq) : type\n\n switch (effectiveType) {\n case 'dna':\n return seq.replace(DNA_CHARS, '')\n case 'rna':\n return seq.replace(RNA_CHARS, '')\n case 'protein':\n return seq.replace(PROTEIN_CHARS, '')\n default:\n return seq\n }\n }\n\n function reverseComplement(seq: string, type: 'dna' | 'rna'): string {\n const complementMap = type === 'dna' ? DNA_COMPLEMENT : RNA_COMPLEMENT\n\n return seq\n .split('')\n .reverse()\n .map(ch => complementMap[ch] ?? ch)\n .join('')\n }\n\n function calculateStats(seq: string, type: 'dna' | 'rna' | 'protein'): SequenceStats {\n const length = seq.length\n const stats: SequenceStats = { length }\n\n if (type === 'dna' || type === 'rna') {\n const upper = seq.toUpperCase()\n let gcCount = 0\n for (const ch of upper) {\n if (ch === 'G' || ch === 'C') gcCount++\n }\n stats.gcPercent = length > 0 ? Math.round((gcCount / length) * 10000) / 100 : 0\n stats.molecularWeight = length * (type === 'dna' ? MW_DNA_NT : MW_RNA_NT)\n } else {\n stats.molecularWeight = length * MW_PROTEIN_AA\n }\n\n return stats\n }\n\n function formatFasta(seq: string, lineWidth: number = 60): string {\n const lines: string[] = []\n for (let i = 0; i < seq.length; i += lineWidth) {\n lines.push(seq.slice(i, i + lineWidth))\n }\n return lines.join('\\n')\n }\n\n return {\n detectSequenceType,\n validateSequence,\n reverseComplement,\n calculateStats,\n formatFasta,\n }\n}\n","<script setup lang=\"ts\">\n/** Textarea for DNA/RNA/protein sequences with auto-detection, validation, GC-content stats, reverse-complement, and FASTA formatting tools. */\nimport { computed } from 'vue'\nimport { useSequenceUtils, type SequenceType, type SequenceStats } from '../composables/useSequenceUtils'\n\ninterface Props {\n modelValue?: string\n type?: SequenceType\n readonly?: boolean\n showStats?: boolean\n showTools?: boolean\n maxLength?: number\n placeholder?: string\n rows?: number\n error?: boolean\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: '',\n type: 'auto',\n readonly: false,\n showStats: true,\n showTools: true,\n rows: 6,\n placeholder: 'Paste or type sequence...',\n error: false,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\ndefineSlots<{\n tools?(props: { sequence: string; type: 'dna' | 'rna' | 'protein'; stats: SequenceStats }): unknown\n}>()\n\nconst { detectSequenceType, validateSequence, reverseComplement, calculateStats, formatFasta } = useSequenceUtils()\n\nconst resolvedType = computed<'dna' | 'rna' | 'protein'>(() => {\n if (props.type !== 'auto') return props.type as 'dna' | 'rna' | 'protein'\n if (!props.modelValue) return 'dna'\n return detectSequenceType(props.modelValue)\n})\n\nconst stats = computed<SequenceStats>(() => {\n if (!props.modelValue) return { length: 0 }\n return calculateStats(props.modelValue, resolvedType.value)\n})\n\nconst viewerLines = computed(() => {\n if (!props.modelValue) return []\n const formatted = formatFasta(props.modelValue, 60)\n return formatted.split('\\n').filter(l => l.length > 0)\n})\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLTextAreaElement\n let value = target.value\n\n if (props.maxLength && value.length > props.maxLength) {\n value = value.slice(0, props.maxLength)\n }\n\n // Validate characters\n value = validateSequence(value, resolvedType.value)\n emit('update:modelValue', value)\n}\n\nfunction doReverseComplement() {\n if (!props.modelValue || resolvedType.value === 'protein') return\n emit('update:modelValue', reverseComplement(props.modelValue, resolvedType.value as 'dna' | 'rna'))\n}\n\nfunction doUppercase() {\n if (!props.modelValue) return\n emit('update:modelValue', props.modelValue.toUpperCase())\n}\n\nfunction doClear() {\n emit('update:modelValue', '')\n}\n\nfunction doCopy() {\n if (!props.modelValue) return\n navigator.clipboard.writeText(props.modelValue)\n}\n\nfunction getBaseClass(char: string): string {\n const c = char.toLowerCase()\n if (c === 'a') return 'mld-sequence-input__base--a'\n if (c === 't') return 'mld-sequence-input__base--t'\n if (c === 'u') return 'mld-sequence-input__base--u'\n if (c === 'g') return 'mld-sequence-input__base--g'\n if (c === 'c') return 'mld-sequence-input__base--c'\n if (c === 'n') return 'mld-sequence-input__base--n'\n return ''\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-sequence-input',\n error ? 'mld-sequence-input--error' : '',\n disabled ? 'mld-sequence-input--disabled' : '',\n ]\"\n >\n <!-- Toolbar -->\n <div v-if=\"showTools && !readonly\" class=\"mld-sequence-input__toolbar\">\n <slot name=\"tools\" :sequence=\"modelValue || ''\" :type=\"resolvedType\" :stats=\"stats\">\n <button\n v-if=\"resolvedType !== 'protein'\"\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doReverseComplement\"\n >\n Rev Comp\n </button>\n <button\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doUppercase\"\n >\n Uppercase\n </button>\n <div class=\"mld-sequence-input__tool-sep\" />\n <button\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doCopy\"\n >\n Copy\n </button>\n <button\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doClear\"\n >\n Clear\n </button>\n </slot>\n\n <span class=\"mld-sequence-input__type-badge\">{{ resolvedType }}</span>\n </div>\n\n <!-- Editor (input mode) -->\n <div v-if=\"!readonly\" class=\"mld-sequence-input__editor\">\n <textarea\n :value=\"modelValue\"\n :rows=\"rows\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :maxlength=\"maxLength\"\n class=\"mld-sequence-input__textarea\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n @input=\"handleInput\"\n />\n </div>\n\n <!-- Viewer (readonly mode) -->\n <div v-else class=\"mld-sequence-input__viewer\">\n <div\n v-for=\"(line, lineIdx) in viewerLines\"\n :key=\"lineIdx\"\n class=\"mld-sequence-input__viewer-line\"\n >\n <span class=\"mld-sequence-input__line-number\">{{ lineIdx * 60 + 1 }}</span>\n <span class=\"mld-sequence-input__line-content\">\n <span\n v-for=\"(char, charIdx) in line.split('')\"\n :key=\"charIdx\"\n :class=\"getBaseClass(char)\"\n >{{ char }}</span>\n </span>\n </div>\n <div v-if=\"!modelValue\" class=\"mld-sequence-input__viewer-line\">\n <span class=\"mld-sequence-input__line-number\" />\n <span class=\"mld-sequence-input__line-content\" style=\"color: var(--text-muted);\">No sequence</span>\n </div>\n </div>\n\n <!-- Stats -->\n <div v-if=\"showStats && modelValue\" class=\"mld-sequence-input__stats\">\n <span>\n <span class=\"mld-sequence-input__stat-label\">Length:</span>\n <span class=\"mld-sequence-input__stat-value\">{{ stats.length }}</span>\n </span>\n <span v-if=\"stats.gcPercent !== undefined\">\n <span class=\"mld-sequence-input__stat-label\">GC:</span>\n <span class=\"mld-sequence-input__stat-value\">{{ stats.gcPercent.toFixed(1) }}%</span>\n </span>\n <span v-if=\"stats.molecularWeight !== undefined\">\n <span class=\"mld-sequence-input__stat-label\">MW:</span>\n <span class=\"mld-sequence-input__stat-value\">~{{ (stats.molecularWeight / 1000).toFixed(1) }} kDa</span>\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sequence-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Textarea for DNA/RNA/protein sequences with auto-detection, validation, GC-content stats, reverse-complement, and FASTA formatting tools. */\nimport { computed } from 'vue'\nimport { useSequenceUtils, type SequenceType, type SequenceStats } from '../composables/useSequenceUtils'\n\ninterface Props {\n modelValue?: string\n type?: SequenceType\n readonly?: boolean\n showStats?: boolean\n showTools?: boolean\n maxLength?: number\n placeholder?: string\n rows?: number\n error?: boolean\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n modelValue: '',\n type: 'auto',\n readonly: false,\n showStats: true,\n showTools: true,\n rows: 6,\n placeholder: 'Paste or type sequence...',\n error: false,\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\ndefineSlots<{\n tools?(props: { sequence: string; type: 'dna' | 'rna' | 'protein'; stats: SequenceStats }): unknown\n}>()\n\nconst { detectSequenceType, validateSequence, reverseComplement, calculateStats, formatFasta } = useSequenceUtils()\n\nconst resolvedType = computed<'dna' | 'rna' | 'protein'>(() => {\n if (props.type !== 'auto') return props.type as 'dna' | 'rna' | 'protein'\n if (!props.modelValue) return 'dna'\n return detectSequenceType(props.modelValue)\n})\n\nconst stats = computed<SequenceStats>(() => {\n if (!props.modelValue) return { length: 0 }\n return calculateStats(props.modelValue, resolvedType.value)\n})\n\nconst viewerLines = computed(() => {\n if (!props.modelValue) return []\n const formatted = formatFasta(props.modelValue, 60)\n return formatted.split('\\n').filter(l => l.length > 0)\n})\n\nfunction handleInput(event: Event) {\n const target = event.target as HTMLTextAreaElement\n let value = target.value\n\n if (props.maxLength && value.length > props.maxLength) {\n value = value.slice(0, props.maxLength)\n }\n\n // Validate characters\n value = validateSequence(value, resolvedType.value)\n emit('update:modelValue', value)\n}\n\nfunction doReverseComplement() {\n if (!props.modelValue || resolvedType.value === 'protein') return\n emit('update:modelValue', reverseComplement(props.modelValue, resolvedType.value as 'dna' | 'rna'))\n}\n\nfunction doUppercase() {\n if (!props.modelValue) return\n emit('update:modelValue', props.modelValue.toUpperCase())\n}\n\nfunction doClear() {\n emit('update:modelValue', '')\n}\n\nfunction doCopy() {\n if (!props.modelValue) return\n navigator.clipboard.writeText(props.modelValue)\n}\n\nfunction getBaseClass(char: string): string {\n const c = char.toLowerCase()\n if (c === 'a') return 'mld-sequence-input__base--a'\n if (c === 't') return 'mld-sequence-input__base--t'\n if (c === 'u') return 'mld-sequence-input__base--u'\n if (c === 'g') return 'mld-sequence-input__base--g'\n if (c === 'c') return 'mld-sequence-input__base--c'\n if (c === 'n') return 'mld-sequence-input__base--n'\n return ''\n}\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-sequence-input',\n error ? 'mld-sequence-input--error' : '',\n disabled ? 'mld-sequence-input--disabled' : '',\n ]\"\n >\n <!-- Toolbar -->\n <div v-if=\"showTools && !readonly\" class=\"mld-sequence-input__toolbar\">\n <slot name=\"tools\" :sequence=\"modelValue || ''\" :type=\"resolvedType\" :stats=\"stats\">\n <button\n v-if=\"resolvedType !== 'protein'\"\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doReverseComplement\"\n >\n Rev Comp\n </button>\n <button\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doUppercase\"\n >\n Uppercase\n </button>\n <div class=\"mld-sequence-input__tool-sep\" />\n <button\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doCopy\"\n >\n Copy\n </button>\n <button\n type=\"button\"\n class=\"mld-sequence-input__tool-btn\"\n :disabled=\"!modelValue || disabled\"\n @click=\"doClear\"\n >\n Clear\n </button>\n </slot>\n\n <span class=\"mld-sequence-input__type-badge\">{{ resolvedType }}</span>\n </div>\n\n <!-- Editor (input mode) -->\n <div v-if=\"!readonly\" class=\"mld-sequence-input__editor\">\n <textarea\n :value=\"modelValue\"\n :rows=\"rows\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :maxlength=\"maxLength\"\n class=\"mld-sequence-input__textarea\"\n spellcheck=\"false\"\n autocomplete=\"off\"\n @input=\"handleInput\"\n />\n </div>\n\n <!-- Viewer (readonly mode) -->\n <div v-else class=\"mld-sequence-input__viewer\">\n <div\n v-for=\"(line, lineIdx) in viewerLines\"\n :key=\"lineIdx\"\n class=\"mld-sequence-input__viewer-line\"\n >\n <span class=\"mld-sequence-input__line-number\">{{ lineIdx * 60 + 1 }}</span>\n <span class=\"mld-sequence-input__line-content\">\n <span\n v-for=\"(char, charIdx) in line.split('')\"\n :key=\"charIdx\"\n :class=\"getBaseClass(char)\"\n >{{ char }}</span>\n </span>\n </div>\n <div v-if=\"!modelValue\" class=\"mld-sequence-input__viewer-line\">\n <span class=\"mld-sequence-input__line-number\" />\n <span class=\"mld-sequence-input__line-content\" style=\"color: var(--text-muted);\">No sequence</span>\n </div>\n </div>\n\n <!-- Stats -->\n <div v-if=\"showStats && modelValue\" class=\"mld-sequence-input__stats\">\n <span>\n <span class=\"mld-sequence-input__stat-label\">Length:</span>\n <span class=\"mld-sequence-input__stat-value\">{{ stats.length }}</span>\n </span>\n <span v-if=\"stats.gcPercent !== undefined\">\n <span class=\"mld-sequence-input__stat-label\">GC:</span>\n <span class=\"mld-sequence-input__stat-value\">{{ stats.gcPercent.toFixed(1) }}%</span>\n </span>\n <span v-if=\"stats.molecularWeight !== undefined\">\n <span class=\"mld-sequence-input__stat-label\">MW:</span>\n <span class=\"mld-sequence-input__stat-value\">~{{ (stats.molecularWeight / 1000).toFixed(1) }} kDa</span>\n </span>\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/sequence-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Numeric input paired with a grouped unit selector, with optional auto-conversion and conversion hint when the unit changes. */\nimport { computed, ref, watch } from 'vue'\nimport type { UnitOption } from '../types'\n\ninterface Props {\n modelValue?: number\n unit?: string\n units: UnitOption[]\n precision?: number\n min?: number\n max?: number\n step?: number\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n convertOnUnitChange?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n precision: undefined,\n step: undefined,\n placeholder: 'Enter value',\n disabled: false,\n error: false,\n size: 'md',\n convertOnUnitChange: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number | undefined]\n 'update:unit': [unit: string]\n 'change': [data: { value: number | undefined; unit: string }]\n}>()\n\nconst conversionHint = ref<string | null>(null)\n\nconst currentUnit = computed(() => props.unit || (props.units.length > 0 ? props.units[0].value : ''))\n\n// Group units by their group property\nconst hasGroups = computed(() => props.units.some(u => u.group))\n\nconst groupedUnits = computed(() => {\n if (!hasGroups.value) return null\n const groups = new Map<string, UnitOption[]>()\n const ungrouped: UnitOption[] = []\n for (const u of props.units) {\n if (u.group) {\n if (!groups.has(u.group)) groups.set(u.group, [])\n groups.get(u.group)!.push(u)\n } else {\n ungrouped.push(u)\n }\n }\n return { groups, ungrouped }\n})\n\nfunction findUnit(value: string): UnitOption | undefined {\n return props.units.find(u => u.value === value)\n}\n\nfunction roundToPrecision(value: number): number {\n if (props.precision === undefined) return value\n const factor = Math.pow(10, props.precision)\n return Math.round(value * factor) / factor\n}\n\nfunction clamp(value: number): number {\n let result = value\n if (props.min !== undefined && result < props.min) result = props.min\n if (props.max !== undefined && result > props.max) result = props.max\n return result\n}\n\nfunction handleValueInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = target.value === '' ? undefined : Number(target.value)\n\n conversionHint.value = null\n\n if (value === undefined || isNaN(value)) {\n emit('update:modelValue', undefined)\n emit('change', { value: undefined, unit: currentUnit.value })\n return\n }\n\n const clamped = clamp(value)\n emit('update:modelValue', clamped)\n emit('change', { value: clamped, unit: currentUnit.value })\n}\n\nfunction handleUnitChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const newUnitValue = target.value\n const oldUnitValue = currentUnit.value\n\n let newValue = props.modelValue\n\n if (props.convertOnUnitChange && newValue !== undefined) {\n const oldUnit = findUnit(oldUnitValue)\n const newUnit = findUnit(newUnitValue)\n\n if (oldUnit?.factor !== undefined && newUnit?.factor !== undefined && newUnit.factor !== 0) {\n const converted = newValue * (oldUnit.factor / newUnit.factor)\n newValue = roundToPrecision(converted)\n newValue = clamp(newValue)\n conversionHint.value = `Converted from ${oldUnit.label} to ${newUnit.label}`\n emit('update:modelValue', newValue)\n }\n }\n\n emit('update:unit', newUnitValue)\n emit('change', { value: newValue, unit: newUnitValue })\n}\n\n// Clear conversion hint after a delay\nwatch(conversionHint, (hint) => {\n if (hint) {\n setTimeout(() => {\n conversionHint.value = null\n }, 3000)\n }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-unit-input',\n error ? 'mld-unit-input--error' : '',\n disabled ? 'mld-unit-input--disabled' : '',\n ]\"\n >\n <div :class=\"['mld-unit-input__controls', `mld-unit-input__controls--${size}`]\">\n <input\n type=\"number\"\n :value=\"modelValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n :class=\"[\n 'mld-unit-input__value',\n `mld-unit-input__value--${size}`,\n disabled ? 'mld-unit-input__value--disabled' : '',\n ]\"\n aria-label=\"Value\"\n @input=\"handleValueInput\"\n />\n\n <div class=\"mld-unit-input__unit\">\n <select\n :value=\"currentUnit\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-unit-input__unit-select',\n `mld-unit-input__unit-select--${size}`,\n ]\"\n aria-label=\"Unit\"\n @change=\"handleUnitChange\"\n >\n <template v-if=\"groupedUnits\">\n <template v-if=\"groupedUnits.ungrouped.length > 0\">\n <option\n v-for=\"u in groupedUnits.ungrouped\"\n :key=\"u.value\"\n :value=\"u.value\"\n >\n {{ u.label }}\n </option>\n </template>\n <optgroup\n v-for=\"[groupLabel, groupUnits] in groupedUnits.groups\"\n :key=\"groupLabel\"\n :label=\"groupLabel\"\n class=\"mld-unit-input__unit-group\"\n >\n <option\n v-for=\"u in groupUnits\"\n :key=\"u.value\"\n :value=\"u.value\"\n >\n {{ u.label }}\n </option>\n </optgroup>\n </template>\n <template v-else>\n <option\n v-for=\"u in units\"\n :key=\"u.value\"\n :value=\"u.value\"\n >\n {{ u.label }}\n </option>\n </template>\n </select>\n </div>\n </div>\n\n <div\n v-if=\"conversionHint\"\n class=\"mld-unit-input__conversion-hint\"\n >\n {{ conversionHint }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/unit-input.css';\n</style>\n","<script setup lang=\"ts\">\n/** Numeric input paired with a grouped unit selector, with optional auto-conversion and conversion hint when the unit changes. */\nimport { computed, ref, watch } from 'vue'\nimport type { UnitOption } from '../types'\n\ninterface Props {\n modelValue?: number\n unit?: string\n units: UnitOption[]\n precision?: number\n min?: number\n max?: number\n step?: number\n placeholder?: string\n disabled?: boolean\n error?: boolean\n size?: 'sm' | 'md' | 'lg'\n convertOnUnitChange?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n precision: undefined,\n step: undefined,\n placeholder: 'Enter value',\n disabled: false,\n error: false,\n size: 'md',\n convertOnUnitChange: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: number | undefined]\n 'update:unit': [unit: string]\n 'change': [data: { value: number | undefined; unit: string }]\n}>()\n\nconst conversionHint = ref<string | null>(null)\n\nconst currentUnit = computed(() => props.unit || (props.units.length > 0 ? props.units[0].value : ''))\n\n// Group units by their group property\nconst hasGroups = computed(() => props.units.some(u => u.group))\n\nconst groupedUnits = computed(() => {\n if (!hasGroups.value) return null\n const groups = new Map<string, UnitOption[]>()\n const ungrouped: UnitOption[] = []\n for (const u of props.units) {\n if (u.group) {\n if (!groups.has(u.group)) groups.set(u.group, [])\n groups.get(u.group)!.push(u)\n } else {\n ungrouped.push(u)\n }\n }\n return { groups, ungrouped }\n})\n\nfunction findUnit(value: string): UnitOption | undefined {\n return props.units.find(u => u.value === value)\n}\n\nfunction roundToPrecision(value: number): number {\n if (props.precision === undefined) return value\n const factor = Math.pow(10, props.precision)\n return Math.round(value * factor) / factor\n}\n\nfunction clamp(value: number): number {\n let result = value\n if (props.min !== undefined && result < props.min) result = props.min\n if (props.max !== undefined && result > props.max) result = props.max\n return result\n}\n\nfunction handleValueInput(event: Event) {\n const target = event.target as HTMLInputElement\n const value = target.value === '' ? undefined : Number(target.value)\n\n conversionHint.value = null\n\n if (value === undefined || isNaN(value)) {\n emit('update:modelValue', undefined)\n emit('change', { value: undefined, unit: currentUnit.value })\n return\n }\n\n const clamped = clamp(value)\n emit('update:modelValue', clamped)\n emit('change', { value: clamped, unit: currentUnit.value })\n}\n\nfunction handleUnitChange(event: Event) {\n const target = event.target as HTMLSelectElement\n const newUnitValue = target.value\n const oldUnitValue = currentUnit.value\n\n let newValue = props.modelValue\n\n if (props.convertOnUnitChange && newValue !== undefined) {\n const oldUnit = findUnit(oldUnitValue)\n const newUnit = findUnit(newUnitValue)\n\n if (oldUnit?.factor !== undefined && newUnit?.factor !== undefined && newUnit.factor !== 0) {\n const converted = newValue * (oldUnit.factor / newUnit.factor)\n newValue = roundToPrecision(converted)\n newValue = clamp(newValue)\n conversionHint.value = `Converted from ${oldUnit.label} to ${newUnit.label}`\n emit('update:modelValue', newValue)\n }\n }\n\n emit('update:unit', newUnitValue)\n emit('change', { value: newValue, unit: newUnitValue })\n}\n\n// Clear conversion hint after a delay\nwatch(conversionHint, (hint) => {\n if (hint) {\n setTimeout(() => {\n conversionHint.value = null\n }, 3000)\n }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mld-unit-input',\n error ? 'mld-unit-input--error' : '',\n disabled ? 'mld-unit-input--disabled' : '',\n ]\"\n >\n <div :class=\"['mld-unit-input__controls', `mld-unit-input__controls--${size}`]\">\n <input\n type=\"number\"\n :value=\"modelValue\"\n :min=\"min\"\n :max=\"max\"\n :step=\"step\"\n :disabled=\"disabled\"\n :placeholder=\"placeholder\"\n :class=\"[\n 'mld-unit-input__value',\n `mld-unit-input__value--${size}`,\n disabled ? 'mld-unit-input__value--disabled' : '',\n ]\"\n aria-label=\"Value\"\n @input=\"handleValueInput\"\n />\n\n <div class=\"mld-unit-input__unit\">\n <select\n :value=\"currentUnit\"\n :disabled=\"disabled\"\n :class=\"[\n 'mld-unit-input__unit-select',\n `mld-unit-input__unit-select--${size}`,\n ]\"\n aria-label=\"Unit\"\n @change=\"handleUnitChange\"\n >\n <template v-if=\"groupedUnits\">\n <template v-if=\"groupedUnits.ungrouped.length > 0\">\n <option\n v-for=\"u in groupedUnits.ungrouped\"\n :key=\"u.value\"\n :value=\"u.value\"\n >\n {{ u.label }}\n </option>\n </template>\n <optgroup\n v-for=\"[groupLabel, groupUnits] in groupedUnits.groups\"\n :key=\"groupLabel\"\n :label=\"groupLabel\"\n class=\"mld-unit-input__unit-group\"\n >\n <option\n v-for=\"u in groupUnits\"\n :key=\"u.value\"\n :value=\"u.value\"\n >\n {{ u.label }}\n </option>\n </optgroup>\n </template>\n <template v-else>\n <option\n v-for=\"u in units\"\n :key=\"u.value\"\n :value=\"u.value\"\n >\n {{ u.label }}\n </option>\n </template>\n </select>\n </div>\n </div>\n\n <div\n v-if=\"conversionHint\"\n class=\"mld-unit-input__conversion-hint\"\n >\n {{ conversionHint }}\n </div>\n </div>\n</template>\n\n<style>\n@import '../styles/components/unit-input.css';\n</style>\n","import { ref, reactive, computed, watch, type Ref } from 'vue'\n\n/**\n * Validation rule function type.\n * Returns error message string if invalid, undefined/null if valid.\n */\nexport type ValidationRule<T = unknown> = (value: T, formData: Record<string, unknown>) => string | undefined | null\n\n/**\n * Field validation rules configuration.\n */\nexport interface FieldRules<T = unknown> {\n required?: boolean | string\n minLength?: number | { value: number; message: string }\n maxLength?: number | { value: number; message: string }\n min?: number | { value: number; message: string }\n max?: number | { value: number; message: string }\n pattern?: RegExp | { value: RegExp; message: string }\n email?: boolean | string\n custom?: ValidationRule<T> | ValidationRule<T>[]\n}\n\n/**\n * Field validation rules configuration.\n */\nexport interface FieldState {\n value: unknown\n error: string | null\n touched: boolean\n dirty: boolean\n}\n\n/**\n * Form state and methods.\n */\nexport interface UseFormReturn<T extends Record<string, unknown>> {\n // Form data (reactive)\n data: T\n\n // Field errors\n errors: Record<string, string | null>\n\n // Field touched state\n touched: Record<string, boolean>\n\n // Field dirty state (value changed from initial)\n dirty: Record<string, boolean>\n\n // Overall form state\n isValid: Ref<boolean>\n isDirty: Ref<boolean>\n isSubmitting: Ref<boolean>\n\n // Methods\n setFieldValue: <K extends keyof T>(field: K, value: T[K]) => void\n setFieldError: (field: string, error: string | null) => void\n setFieldTouched: (field: string, touched?: boolean) => void\n validateField: (field: string) => boolean\n validate: () => boolean\n reset: (values?: Partial<T>) => void\n handleSubmit: (onSubmit: (data: T) => Promise<void> | void) => (e?: Event) => Promise<void>\n getFieldProps: <K extends keyof T>(field: K) => {\n modelValue: T[K]\n 'onUpdate:modelValue': (value: T[K]) => void\n onBlur: () => void\n error: string | null\n }\n}\n\n// Built-in validators\nconst validators = {\n required: (value: unknown, message = 'This field is required'): string | null => {\n if (value === null || value === undefined || value === '') {\n return message\n }\n if (Array.isArray(value) && value.length === 0) {\n return message\n }\n return null\n },\n\n minLength: (value: unknown, min: number, message?: string): string | null => {\n if (typeof value !== 'string') return null\n if (value.length < min) {\n return message || `Must be at least ${min} characters`\n }\n return null\n },\n\n maxLength: (value: unknown, max: number, message?: string): string | null => {\n if (typeof value !== 'string') return null\n if (value.length > max) {\n return message || `Must be at most ${max} characters`\n }\n return null\n },\n\n min: (value: unknown, min: number, message?: string): string | null => {\n if (typeof value !== 'number') return null\n if (value < min) {\n return message || `Must be at least ${min}`\n }\n return null\n },\n\n max: (value: unknown, max: number, message?: string): string | null => {\n if (typeof value !== 'number') return null\n if (value > max) {\n return message || `Must be at most ${max}`\n }\n return null\n },\n\n pattern: (value: unknown, pattern: RegExp, message?: string): string | null => {\n if (typeof value !== 'string') return null\n if (!pattern.test(value)) {\n return message || 'Invalid format'\n }\n return null\n },\n\n email: (value: unknown, message = 'Invalid email address'): string | null => {\n if (typeof value !== 'string' || !value) return null\n const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/\n if (!emailRegex.test(value)) {\n return message\n }\n return null\n },\n}\n\n/**\n * Form state management composable with validation.\n *\n * @param initialValues - Initial form values\n * @param rules - Validation rules for each field\n *\n * @example\n * ```typescript\n * const { data, errors, isValid, handleSubmit, getFieldProps } = useForm(\n * { email: '', password: '' },\n * {\n * email: { required: true, email: true },\n * password: { required: true, minLength: 8 },\n * }\n * )\n *\n * // In template\n * <BaseInput v-bind=\"getFieldProps('email')\" label=\"Email\" />\n * <BaseInput v-bind=\"getFieldProps('password')\" type=\"password\" label=\"Password\" />\n * <BaseButton @click=\"handleSubmit(onSubmit)\" :disabled=\"!isValid\">Submit</BaseButton>\n * ```\n */\n/** Reactive form state with field-level validation, dirty tracking, and submit handling. */\nexport function useForm<T extends Record<string, unknown>>(\n initialValues: T,\n rules: Partial<Record<keyof T, FieldRules>> = {}\n): UseFormReturn<T> {\n // Deep copy initial values so nested objects are not shared\n const _initialValues = structuredClone(initialValues)\n\n // Reactive form data\n const data = reactive(structuredClone(initialValues)) as T\n\n // Field state - use simple Record types for better TS compatibility\n const errors = reactive<Record<string, string | null>>(\n Object.keys(initialValues).reduce((acc, key) => {\n acc[key] = null\n return acc\n }, {} as Record<string, string | null>)\n )\n\n const touched = reactive<Record<string, boolean>>(\n Object.keys(initialValues).reduce((acc, key) => {\n acc[key] = false\n return acc\n }, {} as Record<string, boolean>)\n )\n\n const dirty = reactive<Record<string, boolean>>(\n Object.keys(initialValues).reduce((acc, key) => {\n acc[key] = false\n return acc\n }, {} as Record<string, boolean>)\n )\n\n const isSubmitting = ref(false)\n\n // Watch data changes to track dirty state\n watch(\n () => ({ ...data }),\n (newData) => {\n for (const key of Object.keys(newData)) {\n dirty[key] = newData[key as keyof T] !== _initialValues[key as keyof T]\n }\n },\n { deep: true }\n )\n\n // Validate a single field\n function validateField(field: string): boolean {\n const value = data[field as keyof T]\n const fieldRules = rules[field as keyof T]\n\n if (!fieldRules) {\n errors[field] = null\n return true\n }\n\n // Check required\n if (fieldRules.required) {\n const message = typeof fieldRules.required === 'string' ? fieldRules.required : undefined\n const error = validators.required(value, message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Skip other validations if empty and not required\n if (value === null || value === undefined || value === '') {\n errors[field] = null\n return true\n }\n\n // Check minLength\n if (fieldRules.minLength !== undefined) {\n const config = typeof fieldRules.minLength === 'number'\n ? { value: fieldRules.minLength, message: undefined }\n : fieldRules.minLength\n const error = validators.minLength(value, config.value, config.message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Check maxLength\n if (fieldRules.maxLength !== undefined) {\n const config = typeof fieldRules.maxLength === 'number'\n ? { value: fieldRules.maxLength, message: undefined }\n : fieldRules.maxLength\n const error = validators.maxLength(value, config.value, config.message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Check min\n if (fieldRules.min !== undefined) {\n const config = typeof fieldRules.min === 'number'\n ? { value: fieldRules.min, message: undefined }\n : fieldRules.min\n const error = validators.min(value, config.value, config.message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Check max\n if (fieldRules.max !== undefined) {\n const config = typeof fieldRules.max === 'number'\n ? { value: fieldRules.max, message: undefined }\n : fieldRules.max\n const error = validators.max(value, config.value, config.message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Check pattern\n if (fieldRules.pattern !== undefined) {\n const config = fieldRules.pattern instanceof RegExp\n ? { value: fieldRules.pattern, message: undefined }\n : fieldRules.pattern\n const error = validators.pattern(value, config.value, config.message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Check email\n if (fieldRules.email) {\n const message = typeof fieldRules.email === 'string' ? fieldRules.email : undefined\n const error = validators.email(value, message)\n if (error) {\n errors[field] = error\n return false\n }\n }\n\n // Check custom validators\n if (fieldRules.custom) {\n const customRules = Array.isArray(fieldRules.custom) ? fieldRules.custom : [fieldRules.custom]\n for (const rule of customRules) {\n const error = rule(value, data as Record<string, unknown>)\n if (error) {\n errors[field] = error\n return false\n }\n }\n }\n\n errors[field] = null\n return true\n }\n\n // Validate all fields\n function validate(): boolean {\n let isAllValid = true\n for (const field of Object.keys(data)) {\n if (!validateField(field)) {\n isAllValid = false\n }\n }\n return isAllValid\n }\n\n // Computed overall validity\n const isValid = computed(() => {\n return Object.values(errors).every(error => error === null)\n })\n\n // Computed overall dirty state\n const isDirty = computed(() => {\n return Object.values(dirty).some(d => d)\n })\n\n // Set field value\n function setFieldValue<K extends keyof T>(field: K, value: T[K]): void {\n ;(data as Record<string, unknown>)[field as string] = value\n if (touched[field as string]) {\n validateField(field as string)\n }\n }\n\n // Set field error\n function setFieldError(field: string, error: string | null): void {\n errors[field] = error\n }\n\n // Set field touched\n function setFieldTouched(field: string, isTouched = true): void {\n touched[field] = isTouched\n if (isTouched) {\n validateField(field)\n }\n }\n\n // Reset form\n function reset(values?: Partial<T>): void {\n const resetValues = values ? { ..._initialValues, ...values } : _initialValues\n for (const key of Object.keys(data)) {\n ;(data as Record<string, unknown>)[key] = structuredClone(resetValues[key as keyof T])\n errors[key] = null\n touched[key] = false\n dirty[key] = false\n }\n }\n\n // Handle submit\n function handleSubmit(onSubmit: (data: T) => Promise<void> | void) {\n return async (e?: Event): Promise<void> => {\n e?.preventDefault()\n\n // Mark all fields as touched\n for (const field of Object.keys(data)) {\n touched[field] = true\n }\n\n if (!validate()) {\n return\n }\n\n isSubmitting.value = true\n try {\n await onSubmit(data)\n } finally {\n isSubmitting.value = false\n }\n }\n }\n\n // Get props for a field (for v-bind)\n function getFieldProps<K extends keyof T>(field: K) {\n const fieldStr = field as string\n return {\n modelValue: data[field],\n 'onUpdate:modelValue': (value: T[K]) => setFieldValue(field, value),\n onBlur: () => setFieldTouched(fieldStr),\n error: touched[fieldStr] ? errors[fieldStr] : null,\n }\n }\n\n return {\n data,\n errors,\n touched,\n dirty,\n isValid,\n isDirty,\n isSubmitting,\n setFieldValue,\n setFieldError,\n setFieldTouched,\n validateField,\n validate,\n reset,\n handleSubmit,\n getFieldProps,\n }\n}\n","<script setup lang=\"ts\">\n/** Combined date and time picker with a dropdown calendar, time slots, and 12/24 h format support. */\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","<script setup lang=\"ts\">\n/** Combined date and time picker with a dropdown calendar, time slots, and 12/24 h format support. */\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","import type { Component } from 'vue'\nimport type { FormFieldType } from '../types/form-builder'\n\nimport BaseInput from '../components/BaseInput.vue'\nimport BaseTextarea from '../components/BaseTextarea.vue'\nimport BaseSelect from '../components/BaseSelect.vue'\nimport MultiSelect from '../components/MultiSelect.vue'\nimport BaseCheckbox from '../components/BaseCheckbox.vue'\nimport BaseToggle from '../components/BaseToggle.vue'\nimport BaseRadioGroup from '../components/BaseRadioGroup.vue'\nimport BaseSlider from '../components/BaseSlider.vue'\nimport TagsInput from '../components/TagsInput.vue'\nimport NumberInput from '../components/NumberInput.vue'\nimport DatePicker from '../components/DatePicker.vue'\nimport TimePicker from '../components/TimePicker.vue'\nimport DateTimePicker from '../components/DateTimePicker.vue'\nimport FileUploader from '../components/FileUploader.vue'\nimport FormulaInput from '../components/FormulaInput.vue'\nimport SequenceInput from '../components/SequenceInput.vue'\nimport MoleculeInput from '../components/MoleculeInput.vue'\nimport ConcentrationInput from '../components/ConcentrationInput.vue'\nimport UnitInput from '../components/UnitInput.vue'\n\nexport interface RegistryEntry {\n component: Component\n /** Default props applied to the component. */\n defaults: Record<string, unknown>\n /** Whether the component uses v-model (false for event-only components like FileUploader). */\n vModel: boolean\n}\n\nconst registry: Record<FormFieldType, RegistryEntry> = {\n text: { component: BaseInput, defaults: { type: 'text' }, vModel: true },\n email: { component: BaseInput, defaults: { type: 'email' }, vModel: true },\n password: { component: BaseInput, defaults: { type: 'password' }, vModel: true },\n tel: { component: BaseInput, defaults: { type: 'tel' }, vModel: true },\n url: { component: BaseInput, defaults: { type: 'url' }, vModel: true },\n search: { component: BaseInput, defaults: { type: 'search' }, vModel: true },\n number: { component: NumberInput, defaults: {}, vModel: true },\n textarea: { component: BaseTextarea, defaults: {}, vModel: true },\n select: { component: BaseSelect, defaults: {}, vModel: true },\n multiselect: { component: MultiSelect, defaults: {}, vModel: true },\n checkbox: { component: BaseCheckbox, defaults: {}, vModel: true },\n toggle: { component: BaseToggle, defaults: {}, vModel: true },\n radio: { component: BaseRadioGroup, defaults: {}, vModel: true },\n slider: { component: BaseSlider, defaults: {}, vModel: true },\n tags: { component: TagsInput, defaults: {}, vModel: true },\n date: { component: DatePicker, defaults: {}, vModel: true },\n time: { component: TimePicker, defaults: {}, vModel: true },\n datetime: { component: DateTimePicker, defaults: {}, vModel: true },\n file: { component: FileUploader, defaults: {}, vModel: false },\n formula: { component: FormulaInput, defaults: {}, vModel: true },\n sequence: { component: SequenceInput, defaults: {}, vModel: true },\n molecule: { component: MoleculeInput, defaults: {}, vModel: true },\n concentration: { component: ConcentrationInput, defaults: {}, vModel: true },\n unit: { component: UnitInput, defaults: {}, vModel: true },\n}\n\n/** Return the registry entry for a given field type. Throws if the type is unregistered. */\nexport function getFieldRegistryEntry(type: FormFieldType): RegistryEntry {\n return registry[type]\n}\n\n/** Get the default empty value for a given field type. */\nexport function getTypeDefault(type: FormFieldType): unknown {\n switch (type) {\n case 'checkbox':\n case 'toggle':\n return false\n case 'number':\n case 'slider':\n return undefined\n case 'multiselect':\n case 'tags':\n return []\n default:\n return ''\n }\n}\n","import { ref, computed, watch } from 'vue'\nimport { useForm, type FieldRules, type UseFormReturn } from './useForm'\nimport { getFieldRegistryEntry, getTypeDefault } from './formBuilderRegistry'\nimport type {\n FormSchema,\n FormFieldSchema,\n FormSectionSchema,\n FieldCondition,\n FieldValidation,\n FormEnhancements,\n UseFormBuilderReturn,\n} from '../types/form-builder'\n\n// ---------------------------------------------------------------------------\n// Condition evaluator\n// ---------------------------------------------------------------------------\n\n/**\n * Evaluate a JSON-serializable field condition against the current form data.\n *\n * Supports logical operators (`and`, `or`, `not`) and comparison operators\n * (`eq`, `neq`, `gt`, `lt`, `gte`, `lte`, `in`, `notIn`, `truthy`, `falsy`,\n * `contains`). Returns `true` if the condition passes.\n */\nexport function evaluateCondition(\n condition: FieldCondition,\n data: Record<string, unknown>,\n): boolean {\n if ('and' in condition) {\n return condition.and.every((c) => evaluateCondition(c, data))\n }\n if ('or' in condition) {\n return condition.or.some((c) => evaluateCondition(c, data))\n }\n if ('not' in condition) {\n return !evaluateCondition(condition.not, data)\n }\n\n const value = data[condition.field]\n\n if ('eq' in condition) return value === condition.eq\n if ('neq' in condition) return value !== condition.neq\n if ('gt' in condition) return typeof value === 'number' && value > condition.gt\n if ('lt' in condition) return typeof value === 'number' && value < condition.lt\n if ('gte' in condition) return typeof value === 'number' && value >= condition.gte\n if ('lte' in condition) return typeof value === 'number' && value <= condition.lte\n if ('in' in condition) return condition.in.includes(value)\n if ('notIn' in condition) return !condition.notIn.includes(value)\n if ('truthy' in condition) return !!value\n if ('falsy' in condition) return !value\n if ('contains' in condition) {\n if (typeof value === 'string') return value.includes(condition.contains)\n if (Array.isArray(value)) return value.includes(condition.contains)\n return false\n }\n\n return true\n}\n\n// ---------------------------------------------------------------------------\n// Schema helpers\n// ---------------------------------------------------------------------------\n\n/** Return all sections across steps (wizard) or directly from a flat schema. */\nfunction collectSections(schema: FormSchema): FormSectionSchema[] {\n return schema.steps ? schema.steps.flatMap((step) => step.sections) : schema.sections\n}\n\n/** Return all field schemas in schema order, flattening sections and steps. */\nfunction flattenFields(schema: FormSchema): FormFieldSchema[] {\n return collectSections(schema).flatMap((section) => section.fields)\n}\n\n/** Convert a JSON-safe `FieldValidation` descriptor to a runtime `FieldRules` object. */\nfunction convertValidation(v: FieldValidation): FieldRules {\n const rules: FieldRules = {}\n\n if (v.required !== undefined) rules.required = v.required\n if (v.minLength !== undefined) rules.minLength = v.minLength\n if (v.maxLength !== undefined) rules.maxLength = v.maxLength\n if (v.min !== undefined) rules.min = v.min\n if (v.max !== undefined) rules.max = v.max\n if (v.email !== undefined) rules.email = v.email\n\n if (v.pattern !== undefined) {\n rules.pattern =\n typeof v.pattern === 'string'\n ? new RegExp(v.pattern)\n : { value: new RegExp(v.pattern.value), message: v.pattern.message }\n }\n\n return rules\n}\n\n// ---------------------------------------------------------------------------\n// useFormBuilder\n// ---------------------------------------------------------------------------\n\n/**\n * Drive a `FormSchema` as reactive form state.\n *\n * Builds initial values from schema defaults and `initialData`, derives\n * validation rules from `FieldValidation` descriptors and enhancement\n * validators, evaluates `FieldCondition` expressions for field/section\n * visibility, and wires wizard step navigation when `schema.steps` is set.\n *\n * @param schema - Declarative form or wizard schema.\n * @param initialData - Values that override schema defaults.\n * @param enhancements - TypeScript-only callbacks (dynamic options, validators,\n * submit handler, transform, field-change watcher).\n */\n/** Drives a declarative FormSchema as reactive state with conditional fields, wizard steps, and validation. */\nexport function useFormBuilder<T extends Record<string, unknown> = Record<string, unknown>>(\n schema: FormSchema,\n initialData?: Partial<T>,\n enhancements?: FormEnhancements<T>,\n): UseFormBuilderReturn<T> {\n const fields = flattenFields(schema)\n const sections = collectSections(schema)\n\n // -- Build initial values --------------------------------------------------\n const initialValues = {} as Record<string, unknown>\n for (const field of fields) {\n const key = field.name\n if (initialData && key in initialData) {\n initialValues[key] = (initialData as Record<string, unknown>)[key]\n } else if (field.defaultValue !== undefined) {\n initialValues[key] = field.defaultValue\n } else {\n initialValues[key] = getTypeDefault(field.type)\n }\n }\n\n // -- Build validation rules ------------------------------------------------\n const rules: Partial<Record<string, FieldRules>> = {}\n for (const field of fields) {\n const base: FieldRules = field.validation ? convertValidation(field.validation) : {}\n const enhancement = enhancements?.fields?.[field.name as keyof T]\n\n // Wrap validators to skip hidden fields\n const customValidators: Array<(value: unknown, formData: Record<string, unknown>) => string | undefined | null> = []\n\n if (enhancement?.validate) {\n const fn = enhancement.validate\n customValidators.push((value, formData) => fn(value, formData as T))\n }\n\n if (customValidators.length > 0) {\n base.custom = customValidators\n }\n\n // Wrap all rules to skip hidden fields\n if (Object.keys(base).length > 0) {\n rules[field.name] = base\n }\n }\n\n // -- Create form -----------------------------------------------------------\n const form = useForm(initialValues as T, rules as Partial<Record<keyof T, FieldRules>>)\n\n // -- Visibility ------------------------------------------------------------\n function isFieldVisible(name: string): boolean {\n const enhancement = enhancements?.fields?.[name as keyof T]\n if (enhancement?.visible) {\n return enhancement.visible(form.data as T)\n }\n\n const field = fields.find((f) => f.name === name)\n if (field?.condition) {\n return evaluateCondition(field.condition, form.data as Record<string, unknown>)\n }\n\n return true\n }\n\n function isSectionVisible(id: string): boolean {\n const section = sections.find((s) => s.id === id)\n if (section?.condition) {\n return evaluateCondition(section.condition, form.data as Record<string, unknown>)\n }\n return true\n }\n\n // -- Resolved field props --------------------------------------------------\n function getResolvedFieldProps(field: FormFieldSchema): Record<string, unknown> {\n const entry = getFieldRegistryEntry(field.type)\n const formProps = form.getFieldProps(field.name as keyof T)\n\n const merged: Record<string, unknown> = {\n ...entry.defaults,\n ...(field.props ?? {}),\n }\n\n // Dynamic enhancement props\n const enhancement = enhancements?.fields?.[field.name as keyof T]\n if (enhancement?.props) {\n Object.assign(merged, enhancement.props(form.data as T))\n }\n\n // Dynamic options\n const options = getFieldOptions(field.name)\n if (options) {\n merged.options = options\n }\n\n // Form field bindings\n if (entry.vModel) {\n merged.modelValue = formProps.modelValue\n merged['onUpdate:modelValue'] = formProps['onUpdate:modelValue']\n }\n merged.onBlur = formProps.onBlur\n\n // Error as boolean for components that use boolean error prop\n const errorMsg = formProps.error\n if (errorMsg) {\n merged.error = true\n }\n\n // Schema-level props\n if (field.placeholder) merged.placeholder = field.placeholder\n if (field.size) merged.size = field.size\n if (field.disabled) merged.disabled = true\n if (field.readonly) merged.readonly = true\n\n // Radio group needs a name prop\n if (field.type === 'radio' && !merged.name) {\n merged.name = field.name\n }\n\n return merged\n }\n\n function getFieldOptions(name: string): { label: string; value: unknown }[] | undefined {\n const enhancement = enhancements?.fields?.[name as keyof T]\n if (enhancement?.options) {\n return enhancement.options(form.data as T)\n }\n\n const field = fields.find((f) => f.name === name)\n if (field?.props?.options) {\n return field.props.options as { label: string; value: unknown }[]\n }\n\n return undefined\n }\n\n // -- Wizard state ----------------------------------------------------------\n const currentStep = ref(0)\n\n const isCurrentStepValid = computed(() => {\n if (!schema.steps) return form.isValid.value\n\n const step = schema.steps[currentStep.value]\n if (!step) return true\n\n for (const section of step.sections) {\n if (!isSectionVisible(section.id)) continue\n for (const field of section.fields) {\n if (!isFieldVisible(field.name)) continue\n if (!form.validateField(field.name)) return false\n }\n }\n return true\n })\n\n function goNext(): boolean {\n if (!schema.steps) return false\n\n // Touch and validate all visible fields in current step\n const step = schema.steps[currentStep.value]\n if (!step) return false\n\n let valid = true\n for (const section of step.sections) {\n if (!isSectionVisible(section.id)) continue\n for (const field of section.fields) {\n if (!isFieldVisible(field.name)) continue\n form.setFieldTouched(field.name, true)\n if (!form.validateField(field.name)) valid = false\n }\n }\n\n if (!valid) return false\n\n if (currentStep.value < schema.steps.length - 1) {\n currentStep.value++\n }\n return true\n }\n\n function goBack(): void {\n if (currentStep.value > 0) {\n currentStep.value--\n }\n }\n\n function goToStep(index: number): void {\n if (!schema.steps) return\n if (index >= 0 && index < schema.steps.length) {\n currentStep.value = index\n }\n }\n\n // -- Validate (skip hidden fields) -----------------------------------------\n function validate(): boolean {\n let allValid = true\n for (const field of fields) {\n if (!isFieldVisible(field.name)) continue\n form.setFieldTouched(field.name, true)\n if (!form.validateField(field.name)) {\n allValid = false\n }\n }\n return allValid\n }\n\n // -- Submit ----------------------------------------------------------------\n async function submit(): Promise<void> {\n if (!validate()) return\n\n // Build submission data excluding hidden fields\n let submitData = {} as Record<string, unknown>\n for (const field of fields) {\n if (isFieldVisible(field.name)) {\n submitData[field.name] = (form.data as Record<string, unknown>)[field.name]\n }\n }\n\n if (enhancements?.transform) {\n submitData = enhancements.transform(submitData as T) as Record<string, unknown>\n }\n\n if (enhancements?.onSubmit) {\n form.isSubmitting.value = true\n try {\n await enhancements.onSubmit(submitData as T)\n } finally {\n form.isSubmitting.value = false\n }\n }\n }\n\n // -- Reset -----------------------------------------------------------------\n function reset(values?: Partial<T>): void {\n form.reset(values)\n currentStep.value = 0\n }\n\n // -- onFieldChange wiring --------------------------------------------------\n if (enhancements?.onFieldChange) {\n const callback = enhancements.onFieldChange\n watch(\n () => ({ ...(form.data as Record<string, unknown>) }),\n (newData, oldData) => {\n if (!oldData) return\n for (const key of Object.keys(newData)) {\n if (newData[key] !== oldData[key]) {\n callback(key as keyof T, newData[key], newData as T)\n }\n }\n },\n { deep: true },\n )\n }\n\n return {\n form: form as UseFormReturn<T>,\n rules: rules as Partial<Record<keyof T, FieldRules>>,\n isFieldVisible,\n isSectionVisible,\n fields,\n getResolvedFieldProps,\n getFieldOptions,\n currentStep,\n isCurrentStepValid,\n goNext,\n goBack,\n goToStep,\n validate,\n reset,\n submit,\n }\n}\n","import { ref, computed, type Ref, type ComputedRef } from 'vue'\nimport { useApi } from './useApi'\nimport type { TreeNode, SummaryData } from '../types'\n\nexport interface UseExperimentDataOptions {\n apiBaseUrl?: string\n immediate?: boolean\n}\n\nexport interface UseExperimentDataReturn {\n data: Ref<Record<string, unknown> | null>\n treeData: ComputedRef<TreeNode[]>\n tableData: ComputedRef<Record<string, unknown>[]>\n summaryData: ComputedRef<SummaryData | null>\n isLoading: Ref<boolean>\n error: Ref<string | null>\n fetch: (experimentId: number) => Promise<void>\n refresh: () => Promise<void>\n}\n\n/** Fetches and normalises experiment output data (tree, table, summary) from the platform API. */\nexport function useExperimentData(\n options: UseExperimentDataOptions = {},\n): UseExperimentDataReturn {\n const api = useApi({ baseUrl: options.apiBaseUrl })\n\n const data = ref<Record<string, unknown> | null>(null)\n const isLoading = ref(false)\n const error = ref<string | null>(null)\n let lastExperimentId: number | null = null\n\n const treeData = computed<TreeNode[]>(() => {\n if (!data.value) return []\n const tree = data.value.tree_data ?? data.value.treeData\n return Array.isArray(tree) ? tree as TreeNode[] : []\n })\n\n const tableData = computed<Record<string, unknown>[]>(() => {\n if (!data.value) return []\n const table = data.value.table_data ?? data.value.tableData\n return Array.isArray(table) ? table as Record<string, unknown>[] : []\n })\n\n const summaryData = computed<SummaryData | null>(() => {\n if (!data.value) return null\n const summary = data.value.summary_data ?? data.value.summaryData\n if (summary && typeof summary === 'object' && 'metadata' in (summary as Record<string, unknown>)) {\n return summary as SummaryData\n }\n return null\n })\n\n async function fetchData(experimentId: number): Promise<void> {\n lastExperimentId = experimentId\n isLoading.value = true\n error.value = null\n try {\n const result = await api.get<Record<string, unknown>>(\n `/experiments/${experimentId}/data`,\n )\n data.value = result\n } catch (e) {\n error.value = e instanceof Error ? e.message : 'Failed to fetch experiment data'\n data.value = null\n } finally {\n isLoading.value = false\n }\n }\n\n async function refresh(): Promise<void> {\n if (lastExperimentId !== null) {\n await fetchData(lastExperimentId)\n }\n }\n\n return {\n data,\n treeData,\n tableData,\n summaryData,\n isLoading,\n error,\n fetch: fetchData,\n refresh,\n }\n}\n","import { ref, computed, onUnmounted, type Ref } from 'vue'\nimport type { ScheduleEvent } from '../types/components'\n\nexport type DragType = 'create' | 'resize-top' | 'resize-bottom' | 'move'\n\nexport interface DragState {\n type: DragType\n event?: ScheduleEvent\n startDate: Date\n startY: number\n currentY: number\n dayIndex: number\n currentDayIndex: number\n}\n\nexport interface GhostEvent {\n start: Date\n end: Date\n dayIndex: number\n style: Record<string, string>\n}\n\nexport interface UseScheduleDragOptions {\n slotDuration: Ref<number>\n dayStartHour: Ref<number>\n dayEndHour: Ref<number>\n readonly: Ref<boolean>\n slotHeight: Ref<number>\n blockedSlots?: Ref<{ start: string; end: string; label?: string }[]>\n onCreateComplete?: (start: Date, end: Date) => void\n onMoveComplete?: (event: ScheduleEvent, newStart: Date, newEnd: Date) => void\n onResizeComplete?: (event: ScheduleEvent, newStart: Date, newEnd: Date) => void\n}\n\n/** Handles pointer-driven create, move, and resize drag interactions for the ScheduleCalendar grid. */\nexport function useScheduleDrag(options: UseScheduleDragOptions) {\n const isDragging = ref(false)\n const dragState = ref<DragState | null>(null)\n\n const ghost = computed<GhostEvent | null>(() => {\n if (!dragState.value) return null\n const state = dragState.value\n const slotH = options.slotHeight.value\n const slotMin = options.slotDuration.value\n const dayStart = options.dayStartHour.value * 60\n\n if (state.type === 'create') {\n const deltaY = state.currentY - state.startY\n const startOffset = state.startY\n const endOffset = startOffset + deltaY\n\n const topPx = Math.min(startOffset, endOffset)\n const bottomPx = Math.max(startOffset, endOffset)\n\n const startMinutes = dayStart + Math.round((topPx / slotH) * slotMin)\n const endMinutes = dayStart + Math.round((bottomPx / slotH) * slotMin)\n\n const snappedStart = snapMinutes(startMinutes, slotMin)\n const snappedEnd = Math.max(snapMinutes(endMinutes, slotMin), snappedStart + slotMin)\n\n const snappedTopPx = ((snappedStart - dayStart) / slotMin) * slotH\n const snappedHeightPx = ((snappedEnd - snappedStart) / slotMin) * slotH\n\n return {\n start: minutesToDate(state.startDate, snappedStart),\n end: minutesToDate(state.startDate, snappedEnd),\n dayIndex: state.dayIndex,\n style: {\n top: `${snappedTopPx}px`,\n height: `${snappedHeightPx}px`,\n },\n }\n }\n\n if (state.type === 'move' && state.event) {\n const deltaY = state.currentY - state.startY\n const eventStart = dateToMinutes(new Date(state.event.start))\n const eventEnd = dateToMinutes(new Date(state.event.end))\n const duration = eventEnd - eventStart\n const pixelDelta = Math.round((deltaY / slotH) * slotMin)\n const newStart = snapMinutes(eventStart + pixelDelta, slotMin)\n const newEnd = newStart + duration\n\n const clamped = clampRange(newStart, newEnd, dayStart, options.dayEndHour.value * 60)\n const topPx = ((clamped.start - dayStart) / slotMin) * slotH\n const heightPx = ((clamped.end - clamped.start) / slotMin) * slotH\n\n return {\n start: minutesToDate(state.startDate, clamped.start),\n end: minutesToDate(state.startDate, clamped.end),\n dayIndex: state.currentDayIndex,\n style: {\n top: `${topPx}px`,\n height: `${heightPx}px`,\n },\n }\n }\n\n if ((state.type === 'resize-top' || state.type === 'resize-bottom') && state.event) {\n const eventStart = dateToMinutes(new Date(state.event.start))\n const eventEnd = dateToMinutes(new Date(state.event.end))\n const deltaY = state.currentY - state.startY\n const pixelDelta = Math.round((deltaY / slotH) * slotMin)\n\n let newStart = eventStart\n let newEnd = eventEnd\n\n if (state.type === 'resize-top') {\n newStart = snapMinutes(eventStart + pixelDelta, slotMin)\n newStart = Math.min(newStart, newEnd - slotMin)\n } else {\n newEnd = snapMinutes(eventEnd + pixelDelta, slotMin)\n newEnd = Math.max(newEnd, newStart + slotMin)\n }\n\n const clamped = clampRange(newStart, newEnd, dayStart, options.dayEndHour.value * 60)\n const topPx = ((clamped.start - dayStart) / slotMin) * slotH\n const heightPx = ((clamped.end - clamped.start) / slotMin) * slotH\n\n return {\n start: minutesToDate(state.startDate, clamped.start),\n end: minutesToDate(state.startDate, clamped.end),\n dayIndex: state.dayIndex,\n style: {\n top: `${topPx}px`,\n height: `${heightPx}px`,\n },\n }\n }\n\n return null\n })\n\n function startCreate(date: Date, y: number, dayIndex: number) {\n if (options.readonly.value) return\n isDragging.value = true\n dragState.value = {\n type: 'create',\n startDate: date,\n startY: y,\n currentY: y,\n dayIndex,\n currentDayIndex: dayIndex,\n }\n addListeners()\n }\n\n function startMove(event: ScheduleEvent, y: number, dayIndex: number) {\n if (options.readonly.value || event.draggable === false) return\n isDragging.value = true\n dragState.value = {\n type: 'move',\n event,\n startDate: new Date(event.start),\n startY: y,\n currentY: y,\n dayIndex,\n currentDayIndex: dayIndex,\n }\n addListeners()\n }\n\n function startResize(event: ScheduleEvent, edge: 'top' | 'bottom', y: number, dayIndex: number) {\n if (options.readonly.value || event.resizable === false) return\n isDragging.value = true\n dragState.value = {\n type: edge === 'top' ? 'resize-top' : 'resize-bottom',\n event,\n startDate: new Date(event.start),\n startY: y,\n currentY: y,\n dayIndex,\n currentDayIndex: dayIndex,\n }\n addListeners()\n }\n\n function onPointerMove(e: PointerEvent) {\n if (!dragState.value) return\n dragState.value = { ...dragState.value, currentY: e.clientY }\n }\n\n function onPointerUp() {\n if (!dragState.value || !ghost.value) {\n cleanup()\n return\n }\n\n const g = ghost.value\n const state = dragState.value\n\n if (state.type === 'create' && options.onCreateComplete) {\n options.onCreateComplete(g.start, g.end)\n } else if (state.type === 'move' && state.event && options.onMoveComplete) {\n options.onMoveComplete(state.event, g.start, g.end)\n } else if ((state.type === 'resize-top' || state.type === 'resize-bottom') && state.event && options.onResizeComplete) {\n options.onResizeComplete(state.event, g.start, g.end)\n }\n\n cleanup()\n }\n\n function addListeners() {\n document.addEventListener('pointermove', onPointerMove)\n document.addEventListener('pointerup', onPointerUp)\n }\n\n function cleanup() {\n isDragging.value = false\n dragState.value = null\n document.removeEventListener('pointermove', onPointerMove)\n document.removeEventListener('pointerup', onPointerUp)\n }\n\n onUnmounted(cleanup)\n\n return {\n isDragging,\n dragState,\n ghost,\n startCreate,\n startMove,\n startResize,\n }\n}\n\nfunction snapMinutes(minutes: number, step: number): number {\n return Math.round(minutes / step) * step\n}\n\nfunction dateToMinutes(date: Date): number {\n return date.getHours() * 60 + date.getMinutes()\n}\n\nfunction minutesToDate(baseDate: Date, minutes: number): Date {\n const d = new Date(baseDate)\n d.setHours(Math.floor(minutes / 60), minutes % 60, 0, 0)\n return d\n}\n\nfunction clampRange(start: number, end: number, min: number, max: number) {\n const s = Math.max(start, min)\n const e = Math.min(end, max)\n return { start: s, end: Math.max(e, s) }\n}\n"],"mappings":"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IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACF;EACF;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;IACN;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACN;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACL,KAAK;IACN;GACF;EACF;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACN;GACF;EACF;CACD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,aAAa;EACb,iBAAiB;EACjB,WAAW;EACX,YAAY;GACV;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,SAAS;IACT,SAAS;KACP;MAAE,OAAO;MAAW,OAAO;MAAkB;KAC7C;MAAE,OAAO;MAAU,OAAO;MAAU;KACpC;MAAE,OAAO;MAAU,OAAO;MAAkB;KAC5C;MAAE,OAAO;MAAa,OAAO;MAAa;KAC3C;IACF;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,MAAM;IACN,UAAU;IACV,SAAS;IACT,KAAK;IACN;GACD;IACE,KAAK;IACL,OAAO;IACP,MAAM;IACN,UAAU;IACV,aAAa;IACd;GACF;EACF;CACD;EACE,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"}