@morscherlab/mint-sdk 1.4.11 → 1.4.13

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 (77) hide show
  1. package/dist/{AlertBox-CIhqsWbN.js → AlertBox-CexONPhA.js} +2 -2
  2. package/dist/{AlertBox-CIhqsWbN.js.map → AlertBox-CexONPhA.js.map} +1 -1
  3. package/dist/{AppSidebar-DbftYDAY.js → AppSidebar-CIBehlYL.js} +16 -9
  4. package/dist/{AppSidebar-DbftYDAY.js.map → AppSidebar-CIBehlYL.js.map} +1 -1
  5. package/dist/{ExperimentPopover-BM92LlrQ.js → ExperimentPopover-CzV1--OY.js} +2 -2
  6. package/dist/{ExperimentPopover-BM92LlrQ.js.map → ExperimentPopover-CzV1--OY.js.map} +1 -1
  7. package/dist/{ExperimentSelectorModal-BOLK-Gl4.js → ExperimentSelectorModal-CUkKmgfr.js} +3 -3
  8. package/dist/{ExperimentSelectorModal-BOLK-Gl4.js.map → ExperimentSelectorModal-CUkKmgfr.js.map} +1 -1
  9. package/dist/{SegmentedControl-Dz41vAFM.js → SegmentedControl-C-sckoJZ.js} +2 -2
  10. package/dist/{SegmentedControl-Dz41vAFM.js.map → SegmentedControl-C-sckoJZ.js.map} +1 -1
  11. package/dist/{SettingsModal-CKWC1JNV.js → SettingsModal-jW7Z4i_1.js} +6 -6
  12. package/dist/{SettingsModal-CKWC1JNV.js.map → SettingsModal-jW7Z4i_1.js.map} +1 -1
  13. package/dist/composables/usePluginWorkspace.d.ts +2 -0
  14. package/dist/{controlSchemaModel-C20f-1_T.js → controlSchemaModel-C_XqpkgE.js} +2 -2
  15. package/dist/{controlSchemaModel-C20f-1_T.js.map → controlSchemaModel-C_XqpkgE.js.map} +1 -1
  16. package/dist/{data-C72M20rh.js → data-8G-U6Pb0.js} +9 -9
  17. package/dist/{data-C72M20rh.js.map → data-8G-U6Pb0.js.map} +1 -1
  18. package/dist/data.js +7 -7
  19. package/dist/index.js +16 -16
  20. package/dist/{install-DbF7ddGl.js → install-CM33AZhf.js} +14 -38
  21. package/dist/install-CM33AZhf.js.map +1 -0
  22. package/dist/install.js +1 -1
  23. package/dist/{lab-B5sKmlWF.js → lab-CPJ7n7e5.js} +37 -166
  24. package/dist/lab-CPJ7n7e5.js.map +1 -0
  25. package/dist/lab.js +4 -4
  26. package/dist/{records-CWRztE5M.js → records-BAymyHAn.js} +10 -110
  27. package/dist/records-BAymyHAn.js.map +1 -0
  28. package/dist/{runtime-OBeh3Knt.js → runtime-PntKXQ7K.js} +93 -52
  29. package/dist/runtime-PntKXQ7K.js.map +1 -0
  30. package/dist/runtime.js +6 -6
  31. package/dist/{shell-DDgF2bug.js → shell-B-woqThX.js} +4 -4
  32. package/dist/{shell-DDgF2bug.js.map → shell-B-woqThX.js.map} +1 -1
  33. package/dist/shell.js +4 -4
  34. package/dist/styles.css +61 -105
  35. package/dist/{useExperimentSamples-Cz2v3V_q.js → useExperimentSamples-DEDAKa2E.js} +3 -3
  36. package/dist/{useExperimentSamples-Cz2v3V_q.js.map → useExperimentSamples-DEDAKa2E.js.map} +1 -1
  37. package/dist/{useListReorder-C--K06el.js → useListReorder-W1bzK_bo.js} +2 -2
  38. package/dist/{useListReorder-C--K06el.js.map → useListReorder-W1bzK_bo.js.map} +1 -1
  39. package/dist/{useRackEditor-Cba_0OfI.js → useRackEditor-BWOnob3i.js} +154 -25
  40. package/dist/useRackEditor-BWOnob3i.js.map +1 -0
  41. package/dist/{useRealtime-CNQbrS01.js → useRealtime-B0u_FvEH.js} +2 -2
  42. package/dist/{useRealtime-CNQbrS01.js.map → useRealtime-B0u_FvEH.js.map} +1 -1
  43. package/dist/{useRequest-Bfsh7gIm.js → useRequest-D9-3BS-3.js} +2 -2
  44. package/dist/{useRequest-Bfsh7gIm.js.map → useRequest-D9-3BS-3.js.map} +1 -1
  45. package/package.json +1 -1
  46. package/src/__tests__/components/PluginWorkspaceView.test.ts +19 -0
  47. package/src/__tests__/styles/menusShellRefresh.test.ts +5 -0
  48. package/src/components/FileUploader.vue +3 -35
  49. package/src/components/GeneratedPluginView.vue +35 -81
  50. package/src/components/GeneratedResultRenderer.vue +2 -1
  51. package/src/components/PlotlyChart.vue +1 -1
  52. package/src/components/PluginWorkspaceView.vue +2 -1
  53. package/src/composables/usePluginWorkspace.ts +14 -3
  54. package/src/generated-ui/main.ts +3 -1
  55. package/src/styles/components/alert-box.css +1 -1
  56. package/src/styles/components/app-sidebar.css +5 -5
  57. package/src/styles/components/app-top-bar.css +2 -1
  58. package/src/styles/components/button.css +3 -3
  59. package/src/styles/components/checkbox.css +1 -1
  60. package/src/styles/components/collapsible-card.css +1 -1
  61. package/src/styles/components/confirm-dialog.css +2 -2
  62. package/src/styles/components/file-uploader.css +3 -3
  63. package/src/styles/components/icon-button.css +3 -3
  64. package/src/styles/components/loading-spinner.css +1 -1
  65. package/src/styles/components/pill.css +1 -1
  66. package/src/styles/components/plugin-icon.css +1 -1
  67. package/src/styles/components/presence-avatars.css +4 -4
  68. package/src/styles/components/progress-bar.css +1 -1
  69. package/src/styles/components/sequence-input.css +1 -1
  70. package/src/styles/components/slider.css +1 -1
  71. package/src/styles/components/toggle.css +1 -1
  72. package/src/styles/variables.css +4 -0
  73. package/dist/install-DbF7ddGl.js.map +0 -1
  74. package/dist/lab-B5sKmlWF.js.map +0 -1
  75. package/dist/records-CWRztE5M.js.map +0 -1
  76. package/dist/runtime-OBeh3Knt.js.map +0 -1
  77. package/dist/useRackEditor-Cba_0OfI.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useRackEditor-BWOnob3i.js","names":["$slots","$slots","$slots","$slots"],"sources":["../src/components/DataFrame.vue","../src/components/DataFrame.vue","../src/components/SectionCard.vue","../src/components/SectionCard.vue","../src/utils/sampleTypeMarker.ts","../src/components/WellPlate.colors.ts","../src/components/internal/WellEditPopupInternal.vue","../src/components/internal/WellEditPopupInternal.vue","../src/components/WellPlate.conditions.ts","../src/components/WellPlate.geometry.ts","../src/components/WellPlate.sampleDrop.ts","../src/components/WellPlate.interaction.ts","../src/components/WellPlate.rendering.ts","../src/components/WellPlate.legend.ts","../src/components/WellPlate.vue","../src/components/WellPlate.vue","../src/utils/color.ts","../src/components/PlateEditor.vue","../src/components/PlateEditor.vue","../src/utils/dose.ts","../src/components/DoseCalculatorVolumeField.vue","../src/components/DoseCalculatorVolumeField.vue","../src/components/DoseCalculator.vue","../src/components/DoseCalculator.vue","../src/utils/plotlyTemplate.ts","../src/components/ChartContainer.vue","../src/components/ChartContainer.vue","../src/components/PlotlyChart.vue","../src/components/PlotlyChart.vue","../src/composables/pluginEndpointBuilder.ts","../src/composables/createClient.ts","../src/components/GeneratedPlotlyResult.vue","../src/components/GeneratedPlotlyResult.vue","../src/components/GeneratedResultRenderer.vue","../src/components/GeneratedResultRenderer.vue","../src/composables/useScheduleDrag.ts","../src/composables/useScheduleCalendarLayout.ts","../src/components/ScheduleCalendar.vue","../src/components/ScheduleCalendar.vue","../src/composables/useRackEditor.ts"],"sourcesContent":["<script setup lang=\"ts\">\n/** Data table with sortable columns, search, pagination, and optional row selection. */\nimport { ref, computed, onBeforeUnmount } from 'vue'\nimport type { DataFrameColumn, DataFrameSpec, SortState } from '../types'\nimport { useSortedItems } from '../composables/useSortedItems'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useListSelection } from '../composables/useListSelection'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport DataStateInternal from './internal/DataStateInternal.vue'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport { tableDensityToSize, tryUseSettingsStore } from '../stores/settings'\nimport { applySpecDefaults } from '../composables/componentSpecs'\n\n/** Inbox glyph kept from the pre-1.2 hand-rolled empty state. */\nconst EMPTY_ICON_PATH\n = 'M22 12L16 12L14 15L10 15L8 12L2 12'\n + 'M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'\n\n/**\n * DataFrame - Data table with sorting, searching, pagination, and row selection.\n *\n * The structure (columns, paging, search, selection) is one `:spec` built with\n * `defineDataFrame()`. Rows, loading and error are plain props; sort,\n * selection, page and column widths are `v-model`s.\n *\n * @example\n * ```ts\n * const table = defineDataFrame({\n * columns: [{ key: 'name', label: 'Name', sortable: true }, { key: 'age', label: 'Age', type: 'number' }],\n * rowKey: 'id',\n * searchable: true,\n * pagination: { pageSize: 10 },\n * })\n * ```\n * ```vue\n * <DataFrame :spec=\"table\" :data=\"rows\" v-model:selected=\"selected\" />\n * ```\n */\ninterface Props {\n /** Structure built with defineDataFrame(). Optional: every option has a default. */\n spec?: DataFrameSpec\n /** Array of data objects */\n data: Record<string, unknown>[]\n /** Show loading overlay */\n loading?: boolean\n /** Error message. When set, the table body is replaced by an error state. */\n error?: string | null\n /** Empty state message */\n emptyText?: string\n /** Current sort state. Bind with `v-model:sort`; unbound, the table keeps its own. */\n sort?: SortState | null\n /** Selected row keys. Bind with `v-model:selected`. */\n selected?: (string | number)[]\n /** Current page (1-indexed) when `spec.pagination` is set. Bind with `v-model:page`; unbound, the table keeps its own. */\n page?: number\n /** Column widths in px keyed by column key. Bind with `v-model:column-widths`; unbound, the table keeps its own. */\n columnWidths?: Record<string, number>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n spec: () => ({}),\n loading: false,\n error: null,\n emptyText: 'No data',\n sort: undefined,\n selected: () => [],\n page: undefined,\n columnWidths: undefined,\n})\n\n/**\n * @event update:sort - Emitted when sort state changes\n * @event update:page - Emitted when the page changes\n * @event update:selected - Emitted when row selection changes\n * @event row-click - Emitted when a row is clicked\n * @event cell-click - Emitted when a cell is clicked\n */\nconst emit = defineEmits<{\n 'update:sort': [sort: SortState | null]\n 'update:page': [page: number]\n 'update:selected': [keys: (string | number)[]]\n 'update:columnWidths': [widths: Record<string, number>]\n 'row-click': [row: Record<string, unknown>, index: number]\n 'cell-click': [value: unknown, column: DataFrameColumn, row: Record<string, unknown>]\n 'delete-row': [row: Record<string, unknown>, index: number]\n}>()\n\nconst config = computed(() => applySpecDefaults({\n striped: true,\n bordered: true,\n stickyHeader: false,\n sortable: false,\n pagination: false as const,\n searchable: false,\n searchPlaceholder: 'Search...',\n selectable: false,\n clickableRows: false,\n deletable: false,\n deleteLabel: 'Delete row',\n resizable: false,\n}, props.spec))\n\nconst columns = computed<DataFrameColumn[]>(() =>\n config.value.columns ?? Object.keys(props.data[0] ?? {}).map(key => ({ key, label: key })),\n)\n\nconst settings = tryUseSettingsStore()\n\n/** Explicit `size` wins; otherwise the user's table density, else `md`. */\nconst size = computed<'sm' | 'md' | 'lg'>(() =>\n config.value.size ?? (settings ? tableDensityToSize(settings.tableDensity) : 'md'),\n)\n\nconst searchQuery = ref('')\nconst internalSort = ref<SortState | null>(null)\n\nconst activeSort = computed(() => props.sort !== undefined ? props.sort : internalSort.value)\n\nfunction getRowKey(row: Record<string, unknown>, index: number): string | number {\n const { rowKey } = config.value\n if (!rowKey) return index\n if (typeof rowKey === 'function') return rowKey(row)\n return row[rowKey] as string | number\n}\n\nfunction getCellValue(row: Record<string, unknown>, key: string): unknown {\n return key.split('.').reduce((obj: unknown, k) => {\n if (obj && typeof obj === 'object') return (obj as Record<string, unknown>)[k]\n return undefined\n }, row)\n}\n\nfunction formatCell(col: DataFrameColumn, row: Record<string, unknown>, index: number): string {\n const value = getCellValue(row, col.key)\n if (col.formatter) return col.formatter(value, row, index)\n if (value === null || value === undefined) return ''\n if (typeof value === 'number' && col.precision !== undefined) return value.toFixed(col.precision)\n return String(value)\n}\n\n/** Explicit `align` wins; numeric columns default to the right (2a). */\nfunction columnAlign(col: DataFrameColumn): 'left' | 'center' | 'right' {\n return col.align ?? (col.type === 'number' ? 'right' : 'left')\n}\n\nconst textSearch = useTextSearch({\n items: () => props.data,\n query: searchQuery,\n enabled: () => config.value.searchable,\n getText: (row) => {\n const keys = config.value.searchKeys ?? columns.value.map(c => c.key)\n return keys.map(key => getCellValue(row, key))\n },\n})\nconst filteredData = textSearch.filteredItems\n\nconst tableSort = useSortedItems({\n items: filteredData,\n sort: activeSort,\n enabled: () => {\n const sort = activeSort.value\n return Boolean(sort?.direction && columns.value.some(c => c.key === sort.key))\n },\n getValue: (row, key) => getCellValue(row, key),\n})\nconst sortedData = tableSort.sortedItems\n\n// Pagination: a bound `page` wins; unbound, the table keeps its own.\nconst internalPage = ref(1)\nconst pageSize = computed(() => config.value.pagination ? config.value.pagination.pageSize : 0)\n\nconst totalPages = computed(() => {\n if (!pageSize.value) return 1\n return Math.max(1, Math.ceil(sortedData.value.length / pageSize.value))\n})\n\n// Kept inside `totalPages`, so a search or shorter `data` never leaves an empty page.\nconst currentPage = computed(() => Math.min(props.page ?? internalPage.value, totalPages.value))\n\nconst paginatedData = computed(() => {\n if (!pageSize.value) return sortedData.value\n const start = (currentPage.value - 1) * pageSize.value\n return sortedData.value.slice(start, start + pageSize.value)\n})\n\nfunction handleSort(col: DataFrameColumn) {\n if (!col.sortable && !config.value.sortable) return\n\n const current = activeSort.value\n let next: SortState | null\n\n if (current?.key === col.key) {\n if (current.direction === 'asc') {\n next = { key: col.key, direction: 'desc' }\n } else if (current.direction === 'desc') {\n next = null\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n\n if (props.sort === undefined) {\n internalSort.value = next\n }\n emit('update:sort', next)\n}\n\nfunction handlePageChange(page: number) {\n internalPage.value = page\n emit('update:page', page)\n}\n\nfunction handleRowClick(row: Record<string, unknown>, index: number) {\n emit('row-click', row, index)\n}\n\nfunction handleRowKeydown(event: KeyboardEvent, row: Record<string, unknown>, index: number) {\n if (!config.value.clickableRows) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleRowClick(row, index)\n}\n\nfunction handleCellClick(row: Record<string, unknown>, col: DataFrameColumn, index: number) {\n emit('cell-click', getCellValue(row, col.key), col, row)\n if (config.value.clickableRows) {\n handleRowClick(row, index)\n }\n}\n\nfunction isColumnSortable(col: DataFrameColumn): boolean {\n return col.sortable === true || (config.value.sortable && col.sortable !== false)\n}\n\n/** `aria-sort` for the header cell, omitted on columns that cannot be sorted. */\nfunction columnAriaSort(col: DataFrameColumn): 'ascending' | 'descending' | 'none' | undefined {\n if (!isColumnSortable(col)) return undefined\n if (activeSort.value?.key !== col.key || !activeSort.value.direction) return 'none'\n return activeSort.value.direction === 'asc' ? 'ascending' : 'descending'\n}\n\n// Native buttons activate on Enter/Space, but the default is prevented here so\n// the header sorts exactly once per key press in every engine.\nfunction handleSortKeydown(event: KeyboardEvent, col: DataFrameColumn) {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleSort(col)\n}\n\n// Selection\nconst pageRowKeys = computed(() =>\n paginatedData.value.map((row, i) => getRowKey(row, i))\n)\nconst rowSelection = useListSelection({\n selected: () => props.selected,\n items: pageRowKeys,\n})\nconst allSelected = computed(() => {\n return config.value.selectable && rowSelection.isAllSelected.value\n})\n\nconst selectIndeterminate = computed(() => {\n return props.selected.length > 0 && !allSelected.value\n})\n\nfunction toggleSelectAll() {\n if (allSelected.value) {\n emit('update:selected', [])\n } else {\n emit('update:selected', rowSelection.itemValues.value)\n }\n}\n\nfunction toggleRowSelect(key: string | number) {\n emit('update:selected', rowSelection.toggleValue(key))\n}\n\n// Column resizing — uncontrolled by default; pass `columnWidths` to control it.\nconst MIN_COL_WIDTH = 48\nconst internalWidths = ref<Record<string, number>>({})\nconst activeWidths = computed(() => props.columnWidths ?? internalWidths.value)\n\nfunction toCssSize(value: string | number | undefined): string | undefined {\n if (value === undefined) return undefined\n return typeof value === 'number' ? `${value}px` : value\n}\n\nfunction columnStyle(col: DataFrameColumn) {\n return {\n width: toCssSize(activeWidths.value[col.key] ?? col.width),\n minWidth: toCssSize(col.minWidth),\n }\n}\n\nconst RESIZE_KEY_STEP = 16\n\nfunction columnMinWidth(col: DataFrameColumn): number {\n return Math.max(MIN_COL_WIDTH, typeof col.minWidth === 'number' ? col.minWidth : 0)\n}\n\n/** Current px width of a column when one is known (resized, or a numeric `width`). */\nfunction columnWidthValue(col: DataFrameColumn): number | undefined {\n const width = activeWidths.value[col.key] ?? col.width\n return typeof width === 'number' ? width : undefined\n}\n\nfunction applyColumnWidth(key: string, width: number) {\n const widths = { ...activeWidths.value, [key]: width }\n if (props.columnWidths === undefined) internalWidths.value = widths\n emit('update:columnWidths', widths)\n}\n\n/** Left/Right resize the column by a fixed step; the handle is a `role=\"slider\"`. */\nfunction handleResizeKeydown(event: KeyboardEvent, col: DataFrameColumn) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return\n event.preventDefault()\n const th = (event.target as HTMLElement).closest('th')\n const base =\n columnWidthValue(col) ?? (th instanceof HTMLElement ? th.offsetWidth : MIN_COL_WIDTH)\n const delta = event.key === 'ArrowRight' ? RESIZE_KEY_STEP : -RESIZE_KEY_STEP\n applyColumnWidth(col.key, Math.max(columnMinWidth(col), base + delta))\n}\n\nlet resizeState: { key: string; startX: number; startWidth: number; min: number } | null = null\n\nfunction startResize(col: DataFrameColumn, event: MouseEvent) {\n event.preventDefault()\n event.stopPropagation()\n const th = (event.target as HTMLElement).closest('th')\n const startWidth =\n th instanceof HTMLElement ? th.offsetWidth : (typeof col.width === 'number' ? col.width : MIN_COL_WIDTH)\n resizeState = { key: col.key, startX: event.clientX, startWidth, min: columnMinWidth(col) }\n window.addEventListener('mousemove', onResizeMove)\n window.addEventListener('mouseup', stopResize)\n}\n\nfunction onResizeMove(event: MouseEvent) {\n if (!resizeState) return\n const next = Math.max(resizeState.min, resizeState.startWidth + (event.clientX - resizeState.startX))\n applyColumnWidth(resizeState.key, next)\n}\n\nfunction stopResize() {\n resizeState = null\n window.removeEventListener('mousemove', onResizeMove)\n window.removeEventListener('mouseup', stopResize)\n}\n\nonBeforeUnmount(stopResize)\n\nfunction handleDeleteRow(row: Record<string, unknown>, index: number) {\n emit('delete-row', row, index)\n}\n\nconst wrapperStyle = computed(() => {\n const { maxHeight } = config.value\n if (!maxHeight) return undefined\n return { maxHeight: toCssSize(maxHeight), overflowY: 'auto' as const }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dataframe',\n { 'mint-dataframe--bordered': config.bordered },\n ]\"\n >\n <!-- Search header -->\n <div v-if=\"config.searchable\" class=\"mint-dataframe__header\">\n <div class=\"mint-dataframe__search-wrapper\">\n <svg class=\"mint-dataframe__search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" /><path d=\"m21 21-4.34-4.34\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"mint-dataframe__search\"\n :placeholder=\"config.searchPlaceholder\"\n />\n </div>\n </div>\n\n <!-- Table wrapper -->\n <div class=\"mint-dataframe__table-wrapper\" :style=\"wrapperStyle\">\n <table class=\"mint-dataframe__table\">\n <thead :class=\"['mint-dataframe__thead', { 'mint-dataframe__thead--sticky': config.stickyHeader }]\">\n <tr>\n <th v-if=\"config.selectable\" class=\"mint-dataframe__th mint-dataframe__th--checkbox\">\n <BaseCheckbox\n :model-value=\"allSelected\"\n :indeterminate=\"selectIndeterminate\"\n size=\"sm\"\n aria-label=\"Select all rows\"\n class=\"mint-dataframe__checkbox\"\n @update:model-value=\"toggleSelectAll\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__th',\n `mint-dataframe__th--${size}`,\n `mint-dataframe__th--align-${columnAlign(col)}`,\n {\n 'mint-dataframe__th--sortable': isColumnSortable(col),\n 'mint-dataframe__th--sorted': activeSort?.key === col.key && activeSort?.direction,\n 'mint-dataframe__th--resizable': config.resizable,\n },\n ]\"\n :style=\"columnStyle(col)\"\n :aria-sort=\"columnAriaSort(col)\"\n >\n <component\n :is=\"isColumnSortable(col) ? 'button' : 'div'\"\n :type=\"isColumnSortable(col) ? 'button' : undefined\"\n class=\"mint-dataframe__th-button\"\n @click=\"isColumnSortable(col) ? handleSort(col) : undefined\"\n @keydown=\"isColumnSortable(col) ? handleSortKeydown($event, col) : undefined\"\n >\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n <div class=\"mint-dataframe__th-content\">\n <span class=\"mint-dataframe__th-label\">{{ col.label }}</span>\n <span v-if=\"col.unit\" class=\"mint-dataframe__th-unit\">{{ col.unit }}</span>\n <svg\n v-if=\"isColumnSortable(col)\"\n :class=\"[\n 'mint-dataframe__sort-icon',\n {\n 'mint-dataframe__sort-icon--asc': activeSort?.key === col.key && activeSort?.direction === 'asc',\n 'mint-dataframe__sort-icon--desc': activeSort?.key === col.key && activeSort?.direction === 'desc',\n },\n ]\"\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 v-if=\"activeSort?.key === col.key && activeSort?.direction === 'asc'\" d=\"M12 19V5m-6 6 6-6 6 6\" />\n <path v-else-if=\"activeSort?.key === col.key && activeSort?.direction === 'desc'\" d=\"M12 5v14m6-6-6 6-6-6\" />\n <g v-else><path d=\"m21 16-4 4-4-4\" /><path d=\"M17 20V4\" /><path d=\"m3 8 4-4 4 4\" /><path d=\"M7 4v16\" /></g>\n </svg>\n </div>\n </slot>\n </component>\n <span\n v-if=\"config.resizable\"\n class=\"mint-dataframe__resize-handle\"\n role=\"slider\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n :aria-label=\"`Resize ${col.label} column`\"\n :aria-valuemin=\"columnMinWidth(col)\"\n :aria-valuenow=\"columnWidthValue(col)\"\n @mousedown=\"startResize(col, $event)\"\n @keydown=\"handleResizeKeydown($event, col)\"\n @click.stop\n />\n </th>\n <th\n v-if=\"config.deletable\"\n :class=\"['mint-dataframe__th', `mint-dataframe__th--${size}`, 'mint-dataframe__th--actions']\"\n aria-label=\"Actions\"\n />\n </tr>\n </thead>\n\n <tbody>\n <tr\n v-for=\"(row, rowIndex) in paginatedData\"\n :key=\"getRowKey(row, rowIndex)\"\n :class=\"[\n 'mint-dataframe__row',\n {\n 'mint-dataframe__row--striped': config.striped && rowIndex % 2 === 1,\n 'mint-dataframe__row--selected': rowSelection.isSelected(getRowKey(row, rowIndex)),\n 'mint-dataframe__row--clickable': config.clickableRows,\n },\n ]\"\n :tabindex=\"config.clickableRows ? 0 : undefined\"\n :role=\"config.clickableRows ? 'button' : undefined\"\n @click=\"handleRowClick(row, rowIndex)\"\n @keydown=\"handleRowKeydown($event, row, rowIndex)\"\n >\n <td v-if=\"config.selectable\" class=\"mint-dataframe__td mint-dataframe__td--checkbox\">\n <BaseCheckbox\n :model-value=\"rowSelection.isSelected(getRowKey(row, rowIndex))\"\n size=\"sm\"\n aria-label=\"Select row\"\n class=\"mint-dataframe__checkbox\"\n @click.stop\n @update:model-value=\"toggleRowSelect(getRowKey(row, rowIndex))\"\n />\n </td>\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__td',\n `mint-dataframe__td--${size}`,\n `mint-dataframe__td--align-${columnAlign(col)}`,\n {\n 'mint-dataframe__td--ellipsis': col.ellipsis,\n 'mint-dataframe__td--number': col.type === 'number',\n },\n ]\"\n @click.stop=\"handleCellClick(row, col, rowIndex)\"\n >\n <slot :name=\"`cell-${col.key}`\" :value=\"getCellValue(row, col.key)\" :row=\"row\" :column=\"col\" :index=\"rowIndex\">\n {{ formatCell(col, row, rowIndex) }}\n </slot>\n </td>\n <td\n v-if=\"config.deletable\"\n :class=\"['mint-dataframe__td', `mint-dataframe__td--${size}`, 'mint-dataframe__td--actions']\"\n >\n <button\n type=\"button\"\n class=\"mint-dataframe__delete-btn\"\n :aria-label=\"config.deleteLabel\"\n :title=\"config.deleteLabel\"\n @click.stop=\"handleDeleteRow(row, rowIndex)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3 6h18\" /><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" /><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" /><path d=\"M10 11v6\" /><path d=\"M14 11v6\" />\n </svg>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Error state -->\n <div v-if=\"error\" class=\"mint-dataframe__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" :title=\"error\" />\n </slot>\n </div>\n\n <!-- Empty state -->\n <div v-else-if=\"paginatedData.length === 0 && !loading\" class=\"mint-dataframe__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" :title=\"emptyText\" :icon-path=\"EMPTY_ICON_PATH\" />\n </slot>\n </div>\n\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-dataframe__loading\">\n <slot name=\"loading\">\n <LoadingSpinner size=\"lg\" />\n </slot>\n </div>\n </div>\n\n <!-- Footer / Pagination -->\n <div v-if=\"pageSize\" class=\"mint-dataframe__footer\">\n <slot name=\"footer\">\n <span class=\"mint-dataframe__page-info\">\n {{ (currentPage - 1) * pageSize + 1 }}&ndash;{{ Math.min(currentPage * pageSize, sortedData.length) }}\n of {{ sortedData.length }}\n </span>\n <div class=\"mint-dataframe__page-controls\">\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"currentPage <= 1\"\n @click=\"handlePageChange(currentPage - 1)\"\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=\"mint-dataframe__page-number\">{{ currentPage }} / {{ totalPages }}</span>\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"currentPage >= totalPages\"\n @click=\"handlePageChange(currentPage + 1)\"\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 </slot>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Data table with sortable columns, search, pagination, and optional row selection. */\nimport { ref, computed, onBeforeUnmount } from 'vue'\nimport type { DataFrameColumn, DataFrameSpec, SortState } from '../types'\nimport { useSortedItems } from '../composables/useSortedItems'\nimport { useTextSearch } from '../composables/useTextSearch'\nimport { useListSelection } from '../composables/useListSelection'\nimport BaseCheckbox from './BaseCheckbox.vue'\nimport DataStateInternal from './internal/DataStateInternal.vue'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport { tableDensityToSize, tryUseSettingsStore } from '../stores/settings'\nimport { applySpecDefaults } from '../composables/componentSpecs'\n\n/** Inbox glyph kept from the pre-1.2 hand-rolled empty state. */\nconst EMPTY_ICON_PATH\n = 'M22 12L16 12L14 15L10 15L8 12L2 12'\n + 'M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z'\n\n/**\n * DataFrame - Data table with sorting, searching, pagination, and row selection.\n *\n * The structure (columns, paging, search, selection) is one `:spec` built with\n * `defineDataFrame()`. Rows, loading and error are plain props; sort,\n * selection, page and column widths are `v-model`s.\n *\n * @example\n * ```ts\n * const table = defineDataFrame({\n * columns: [{ key: 'name', label: 'Name', sortable: true }, { key: 'age', label: 'Age', type: 'number' }],\n * rowKey: 'id',\n * searchable: true,\n * pagination: { pageSize: 10 },\n * })\n * ```\n * ```vue\n * <DataFrame :spec=\"table\" :data=\"rows\" v-model:selected=\"selected\" />\n * ```\n */\ninterface Props {\n /** Structure built with defineDataFrame(). Optional: every option has a default. */\n spec?: DataFrameSpec\n /** Array of data objects */\n data: Record<string, unknown>[]\n /** Show loading overlay */\n loading?: boolean\n /** Error message. When set, the table body is replaced by an error state. */\n error?: string | null\n /** Empty state message */\n emptyText?: string\n /** Current sort state. Bind with `v-model:sort`; unbound, the table keeps its own. */\n sort?: SortState | null\n /** Selected row keys. Bind with `v-model:selected`. */\n selected?: (string | number)[]\n /** Current page (1-indexed) when `spec.pagination` is set. Bind with `v-model:page`; unbound, the table keeps its own. */\n page?: number\n /** Column widths in px keyed by column key. Bind with `v-model:column-widths`; unbound, the table keeps its own. */\n columnWidths?: Record<string, number>\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n spec: () => ({}),\n loading: false,\n error: null,\n emptyText: 'No data',\n sort: undefined,\n selected: () => [],\n page: undefined,\n columnWidths: undefined,\n})\n\n/**\n * @event update:sort - Emitted when sort state changes\n * @event update:page - Emitted when the page changes\n * @event update:selected - Emitted when row selection changes\n * @event row-click - Emitted when a row is clicked\n * @event cell-click - Emitted when a cell is clicked\n */\nconst emit = defineEmits<{\n 'update:sort': [sort: SortState | null]\n 'update:page': [page: number]\n 'update:selected': [keys: (string | number)[]]\n 'update:columnWidths': [widths: Record<string, number>]\n 'row-click': [row: Record<string, unknown>, index: number]\n 'cell-click': [value: unknown, column: DataFrameColumn, row: Record<string, unknown>]\n 'delete-row': [row: Record<string, unknown>, index: number]\n}>()\n\nconst config = computed(() => applySpecDefaults({\n striped: true,\n bordered: true,\n stickyHeader: false,\n sortable: false,\n pagination: false as const,\n searchable: false,\n searchPlaceholder: 'Search...',\n selectable: false,\n clickableRows: false,\n deletable: false,\n deleteLabel: 'Delete row',\n resizable: false,\n}, props.spec))\n\nconst columns = computed<DataFrameColumn[]>(() =>\n config.value.columns ?? Object.keys(props.data[0] ?? {}).map(key => ({ key, label: key })),\n)\n\nconst settings = tryUseSettingsStore()\n\n/** Explicit `size` wins; otherwise the user's table density, else `md`. */\nconst size = computed<'sm' | 'md' | 'lg'>(() =>\n config.value.size ?? (settings ? tableDensityToSize(settings.tableDensity) : 'md'),\n)\n\nconst searchQuery = ref('')\nconst internalSort = ref<SortState | null>(null)\n\nconst activeSort = computed(() => props.sort !== undefined ? props.sort : internalSort.value)\n\nfunction getRowKey(row: Record<string, unknown>, index: number): string | number {\n const { rowKey } = config.value\n if (!rowKey) return index\n if (typeof rowKey === 'function') return rowKey(row)\n return row[rowKey] as string | number\n}\n\nfunction getCellValue(row: Record<string, unknown>, key: string): unknown {\n return key.split('.').reduce((obj: unknown, k) => {\n if (obj && typeof obj === 'object') return (obj as Record<string, unknown>)[k]\n return undefined\n }, row)\n}\n\nfunction formatCell(col: DataFrameColumn, row: Record<string, unknown>, index: number): string {\n const value = getCellValue(row, col.key)\n if (col.formatter) return col.formatter(value, row, index)\n if (value === null || value === undefined) return ''\n if (typeof value === 'number' && col.precision !== undefined) return value.toFixed(col.precision)\n return String(value)\n}\n\n/** Explicit `align` wins; numeric columns default to the right (2a). */\nfunction columnAlign(col: DataFrameColumn): 'left' | 'center' | 'right' {\n return col.align ?? (col.type === 'number' ? 'right' : 'left')\n}\n\nconst textSearch = useTextSearch({\n items: () => props.data,\n query: searchQuery,\n enabled: () => config.value.searchable,\n getText: (row) => {\n const keys = config.value.searchKeys ?? columns.value.map(c => c.key)\n return keys.map(key => getCellValue(row, key))\n },\n})\nconst filteredData = textSearch.filteredItems\n\nconst tableSort = useSortedItems({\n items: filteredData,\n sort: activeSort,\n enabled: () => {\n const sort = activeSort.value\n return Boolean(sort?.direction && columns.value.some(c => c.key === sort.key))\n },\n getValue: (row, key) => getCellValue(row, key),\n})\nconst sortedData = tableSort.sortedItems\n\n// Pagination: a bound `page` wins; unbound, the table keeps its own.\nconst internalPage = ref(1)\nconst pageSize = computed(() => config.value.pagination ? config.value.pagination.pageSize : 0)\n\nconst totalPages = computed(() => {\n if (!pageSize.value) return 1\n return Math.max(1, Math.ceil(sortedData.value.length / pageSize.value))\n})\n\n// Kept inside `totalPages`, so a search or shorter `data` never leaves an empty page.\nconst currentPage = computed(() => Math.min(props.page ?? internalPage.value, totalPages.value))\n\nconst paginatedData = computed(() => {\n if (!pageSize.value) return sortedData.value\n const start = (currentPage.value - 1) * pageSize.value\n return sortedData.value.slice(start, start + pageSize.value)\n})\n\nfunction handleSort(col: DataFrameColumn) {\n if (!col.sortable && !config.value.sortable) return\n\n const current = activeSort.value\n let next: SortState | null\n\n if (current?.key === col.key) {\n if (current.direction === 'asc') {\n next = { key: col.key, direction: 'desc' }\n } else if (current.direction === 'desc') {\n next = null\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n } else {\n next = { key: col.key, direction: 'asc' }\n }\n\n if (props.sort === undefined) {\n internalSort.value = next\n }\n emit('update:sort', next)\n}\n\nfunction handlePageChange(page: number) {\n internalPage.value = page\n emit('update:page', page)\n}\n\nfunction handleRowClick(row: Record<string, unknown>, index: number) {\n emit('row-click', row, index)\n}\n\nfunction handleRowKeydown(event: KeyboardEvent, row: Record<string, unknown>, index: number) {\n if (!config.value.clickableRows) return\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleRowClick(row, index)\n}\n\nfunction handleCellClick(row: Record<string, unknown>, col: DataFrameColumn, index: number) {\n emit('cell-click', getCellValue(row, col.key), col, row)\n if (config.value.clickableRows) {\n handleRowClick(row, index)\n }\n}\n\nfunction isColumnSortable(col: DataFrameColumn): boolean {\n return col.sortable === true || (config.value.sortable && col.sortable !== false)\n}\n\n/** `aria-sort` for the header cell, omitted on columns that cannot be sorted. */\nfunction columnAriaSort(col: DataFrameColumn): 'ascending' | 'descending' | 'none' | undefined {\n if (!isColumnSortable(col)) return undefined\n if (activeSort.value?.key !== col.key || !activeSort.value.direction) return 'none'\n return activeSort.value.direction === 'asc' ? 'ascending' : 'descending'\n}\n\n// Native buttons activate on Enter/Space, but the default is prevented here so\n// the header sorts exactly once per key press in every engine.\nfunction handleSortKeydown(event: KeyboardEvent, col: DataFrameColumn) {\n if (event.key !== 'Enter' && event.key !== ' ') return\n event.preventDefault()\n handleSort(col)\n}\n\n// Selection\nconst pageRowKeys = computed(() =>\n paginatedData.value.map((row, i) => getRowKey(row, i))\n)\nconst rowSelection = useListSelection({\n selected: () => props.selected,\n items: pageRowKeys,\n})\nconst allSelected = computed(() => {\n return config.value.selectable && rowSelection.isAllSelected.value\n})\n\nconst selectIndeterminate = computed(() => {\n return props.selected.length > 0 && !allSelected.value\n})\n\nfunction toggleSelectAll() {\n if (allSelected.value) {\n emit('update:selected', [])\n } else {\n emit('update:selected', rowSelection.itemValues.value)\n }\n}\n\nfunction toggleRowSelect(key: string | number) {\n emit('update:selected', rowSelection.toggleValue(key))\n}\n\n// Column resizing — uncontrolled by default; pass `columnWidths` to control it.\nconst MIN_COL_WIDTH = 48\nconst internalWidths = ref<Record<string, number>>({})\nconst activeWidths = computed(() => props.columnWidths ?? internalWidths.value)\n\nfunction toCssSize(value: string | number | undefined): string | undefined {\n if (value === undefined) return undefined\n return typeof value === 'number' ? `${value}px` : value\n}\n\nfunction columnStyle(col: DataFrameColumn) {\n return {\n width: toCssSize(activeWidths.value[col.key] ?? col.width),\n minWidth: toCssSize(col.minWidth),\n }\n}\n\nconst RESIZE_KEY_STEP = 16\n\nfunction columnMinWidth(col: DataFrameColumn): number {\n return Math.max(MIN_COL_WIDTH, typeof col.minWidth === 'number' ? col.minWidth : 0)\n}\n\n/** Current px width of a column when one is known (resized, or a numeric `width`). */\nfunction columnWidthValue(col: DataFrameColumn): number | undefined {\n const width = activeWidths.value[col.key] ?? col.width\n return typeof width === 'number' ? width : undefined\n}\n\nfunction applyColumnWidth(key: string, width: number) {\n const widths = { ...activeWidths.value, [key]: width }\n if (props.columnWidths === undefined) internalWidths.value = widths\n emit('update:columnWidths', widths)\n}\n\n/** Left/Right resize the column by a fixed step; the handle is a `role=\"slider\"`. */\nfunction handleResizeKeydown(event: KeyboardEvent, col: DataFrameColumn) {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return\n event.preventDefault()\n const th = (event.target as HTMLElement).closest('th')\n const base =\n columnWidthValue(col) ?? (th instanceof HTMLElement ? th.offsetWidth : MIN_COL_WIDTH)\n const delta = event.key === 'ArrowRight' ? RESIZE_KEY_STEP : -RESIZE_KEY_STEP\n applyColumnWidth(col.key, Math.max(columnMinWidth(col), base + delta))\n}\n\nlet resizeState: { key: string; startX: number; startWidth: number; min: number } | null = null\n\nfunction startResize(col: DataFrameColumn, event: MouseEvent) {\n event.preventDefault()\n event.stopPropagation()\n const th = (event.target as HTMLElement).closest('th')\n const startWidth =\n th instanceof HTMLElement ? th.offsetWidth : (typeof col.width === 'number' ? col.width : MIN_COL_WIDTH)\n resizeState = { key: col.key, startX: event.clientX, startWidth, min: columnMinWidth(col) }\n window.addEventListener('mousemove', onResizeMove)\n window.addEventListener('mouseup', stopResize)\n}\n\nfunction onResizeMove(event: MouseEvent) {\n if (!resizeState) return\n const next = Math.max(resizeState.min, resizeState.startWidth + (event.clientX - resizeState.startX))\n applyColumnWidth(resizeState.key, next)\n}\n\nfunction stopResize() {\n resizeState = null\n window.removeEventListener('mousemove', onResizeMove)\n window.removeEventListener('mouseup', stopResize)\n}\n\nonBeforeUnmount(stopResize)\n\nfunction handleDeleteRow(row: Record<string, unknown>, index: number) {\n emit('delete-row', row, index)\n}\n\nconst wrapperStyle = computed(() => {\n const { maxHeight } = config.value\n if (!maxHeight) return undefined\n return { maxHeight: toCssSize(maxHeight), overflowY: 'auto' as const }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dataframe',\n { 'mint-dataframe--bordered': config.bordered },\n ]\"\n >\n <!-- Search header -->\n <div v-if=\"config.searchable\" class=\"mint-dataframe__header\">\n <div class=\"mint-dataframe__search-wrapper\">\n <svg class=\"mint-dataframe__search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"11\" cy=\"11\" r=\"8\" /><path d=\"m21 21-4.34-4.34\" />\n </svg>\n <input\n v-model=\"searchQuery\"\n type=\"text\"\n class=\"mint-dataframe__search\"\n :placeholder=\"config.searchPlaceholder\"\n />\n </div>\n </div>\n\n <!-- Table wrapper -->\n <div class=\"mint-dataframe__table-wrapper\" :style=\"wrapperStyle\">\n <table class=\"mint-dataframe__table\">\n <thead :class=\"['mint-dataframe__thead', { 'mint-dataframe__thead--sticky': config.stickyHeader }]\">\n <tr>\n <th v-if=\"config.selectable\" class=\"mint-dataframe__th mint-dataframe__th--checkbox\">\n <BaseCheckbox\n :model-value=\"allSelected\"\n :indeterminate=\"selectIndeterminate\"\n size=\"sm\"\n aria-label=\"Select all rows\"\n class=\"mint-dataframe__checkbox\"\n @update:model-value=\"toggleSelectAll\"\n />\n </th>\n <th\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__th',\n `mint-dataframe__th--${size}`,\n `mint-dataframe__th--align-${columnAlign(col)}`,\n {\n 'mint-dataframe__th--sortable': isColumnSortable(col),\n 'mint-dataframe__th--sorted': activeSort?.key === col.key && activeSort?.direction,\n 'mint-dataframe__th--resizable': config.resizable,\n },\n ]\"\n :style=\"columnStyle(col)\"\n :aria-sort=\"columnAriaSort(col)\"\n >\n <component\n :is=\"isColumnSortable(col) ? 'button' : 'div'\"\n :type=\"isColumnSortable(col) ? 'button' : undefined\"\n class=\"mint-dataframe__th-button\"\n @click=\"isColumnSortable(col) ? handleSort(col) : undefined\"\n @keydown=\"isColumnSortable(col) ? handleSortKeydown($event, col) : undefined\"\n >\n <slot :name=\"`header-${col.key}`\" :column=\"col\">\n <div class=\"mint-dataframe__th-content\">\n <span class=\"mint-dataframe__th-label\">{{ col.label }}</span>\n <span v-if=\"col.unit\" class=\"mint-dataframe__th-unit\">{{ col.unit }}</span>\n <svg\n v-if=\"isColumnSortable(col)\"\n :class=\"[\n 'mint-dataframe__sort-icon',\n {\n 'mint-dataframe__sort-icon--asc': activeSort?.key === col.key && activeSort?.direction === 'asc',\n 'mint-dataframe__sort-icon--desc': activeSort?.key === col.key && activeSort?.direction === 'desc',\n },\n ]\"\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 v-if=\"activeSort?.key === col.key && activeSort?.direction === 'asc'\" d=\"M12 19V5m-6 6 6-6 6 6\" />\n <path v-else-if=\"activeSort?.key === col.key && activeSort?.direction === 'desc'\" d=\"M12 5v14m6-6-6 6-6-6\" />\n <g v-else><path d=\"m21 16-4 4-4-4\" /><path d=\"M17 20V4\" /><path d=\"m3 8 4-4 4 4\" /><path d=\"M7 4v16\" /></g>\n </svg>\n </div>\n </slot>\n </component>\n <span\n v-if=\"config.resizable\"\n class=\"mint-dataframe__resize-handle\"\n role=\"slider\"\n tabindex=\"0\"\n aria-orientation=\"vertical\"\n :aria-label=\"`Resize ${col.label} column`\"\n :aria-valuemin=\"columnMinWidth(col)\"\n :aria-valuenow=\"columnWidthValue(col)\"\n @mousedown=\"startResize(col, $event)\"\n @keydown=\"handleResizeKeydown($event, col)\"\n @click.stop\n />\n </th>\n <th\n v-if=\"config.deletable\"\n :class=\"['mint-dataframe__th', `mint-dataframe__th--${size}`, 'mint-dataframe__th--actions']\"\n aria-label=\"Actions\"\n />\n </tr>\n </thead>\n\n <tbody>\n <tr\n v-for=\"(row, rowIndex) in paginatedData\"\n :key=\"getRowKey(row, rowIndex)\"\n :class=\"[\n 'mint-dataframe__row',\n {\n 'mint-dataframe__row--striped': config.striped && rowIndex % 2 === 1,\n 'mint-dataframe__row--selected': rowSelection.isSelected(getRowKey(row, rowIndex)),\n 'mint-dataframe__row--clickable': config.clickableRows,\n },\n ]\"\n :tabindex=\"config.clickableRows ? 0 : undefined\"\n :role=\"config.clickableRows ? 'button' : undefined\"\n @click=\"handleRowClick(row, rowIndex)\"\n @keydown=\"handleRowKeydown($event, row, rowIndex)\"\n >\n <td v-if=\"config.selectable\" class=\"mint-dataframe__td mint-dataframe__td--checkbox\">\n <BaseCheckbox\n :model-value=\"rowSelection.isSelected(getRowKey(row, rowIndex))\"\n size=\"sm\"\n aria-label=\"Select row\"\n class=\"mint-dataframe__checkbox\"\n @click.stop\n @update:model-value=\"toggleRowSelect(getRowKey(row, rowIndex))\"\n />\n </td>\n <td\n v-for=\"col in columns\"\n :key=\"col.key\"\n :class=\"[\n 'mint-dataframe__td',\n `mint-dataframe__td--${size}`,\n `mint-dataframe__td--align-${columnAlign(col)}`,\n {\n 'mint-dataframe__td--ellipsis': col.ellipsis,\n 'mint-dataframe__td--number': col.type === 'number',\n },\n ]\"\n @click.stop=\"handleCellClick(row, col, rowIndex)\"\n >\n <slot :name=\"`cell-${col.key}`\" :value=\"getCellValue(row, col.key)\" :row=\"row\" :column=\"col\" :index=\"rowIndex\">\n {{ formatCell(col, row, rowIndex) }}\n </slot>\n </td>\n <td\n v-if=\"config.deletable\"\n :class=\"['mint-dataframe__td', `mint-dataframe__td--${size}`, 'mint-dataframe__td--actions']\"\n >\n <button\n type=\"button\"\n class=\"mint-dataframe__delete-btn\"\n :aria-label=\"config.deleteLabel\"\n :title=\"config.deleteLabel\"\n @click.stop=\"handleDeleteRow(row, rowIndex)\"\n >\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M3 6h18\" /><path d=\"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\" /><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\" /><path d=\"M10 11v6\" /><path d=\"M14 11v6\" />\n </svg>\n </button>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Error state -->\n <div v-if=\"error\" class=\"mint-dataframe__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" :title=\"error\" />\n </slot>\n </div>\n\n <!-- Empty state -->\n <div v-else-if=\"paginatedData.length === 0 && !loading\" class=\"mint-dataframe__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" :title=\"emptyText\" :icon-path=\"EMPTY_ICON_PATH\" />\n </slot>\n </div>\n\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-dataframe__loading\">\n <slot name=\"loading\">\n <LoadingSpinner size=\"lg\" />\n </slot>\n </div>\n </div>\n\n <!-- Footer / Pagination -->\n <div v-if=\"pageSize\" class=\"mint-dataframe__footer\">\n <slot name=\"footer\">\n <span class=\"mint-dataframe__page-info\">\n {{ (currentPage - 1) * pageSize + 1 }}&ndash;{{ Math.min(currentPage * pageSize, sortedData.length) }}\n of {{ sortedData.length }}\n </span>\n <div class=\"mint-dataframe__page-controls\">\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"currentPage <= 1\"\n @click=\"handlePageChange(currentPage - 1)\"\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=\"mint-dataframe__page-number\">{{ currentPage }} / {{ totalPages }}</span>\n <button\n type=\"button\"\n class=\"mint-dataframe__page-btn\"\n :disabled=\"currentPage >= totalPages\"\n @click=\"handlePageChange(currentPage + 1)\"\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 </slot>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** One-card page region from the MINT 1.1 card system: 12px radius, the\n * single --card-shadow token, a header strip (icon chip, title, mono count,\n * actions), and hairline-separated content — no card-in-card. */\nimport { computed, useId } from 'vue'\nimport type { SectionCardAccent, SectionCardDivider } from '../types'\n\ninterface Props {\n title: string\n /** Monospace meta shown next to the title, e.g. \"14 ongoing · 42 total\". */\n count?: string | number\n /** SVG path(s) for the 22px icon chip; omit to render no chip. */\n icon?: string | string[]\n /** Tint of the icon chip. */\n accent?: SectionCardAccent\n /** Uppercase category heading (home cards) vs plain heading (detail cards). */\n uppercase?: boolean\n /** Header hairline: 'strong' = --border-color, 'light' = --border-light. */\n divider?: SectionCardDivider\n /**\n * Stretch to fill the height its parent offers, scrolling the body and\n * pinning the header and footer. Requires a parent that gives the card a\n * definite height (a flex or grid track).\n */\n fill?: boolean\n /** The title toggles the body; bind `v-model:collapsed` to control or keep the state. */\n collapsible?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n count: undefined,\n icon: undefined,\n accent: 'primary',\n uppercase: false,\n divider: 'strong',\n fill: false,\n collapsible: false,\n})\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst bodyId = useId()\n/** `collapsed` hides content only on a card that also shows the toggle to open it again. */\nconst hidden = computed(() => props.collapsible && collapsed.value)\n\nconst iconPaths = computed(() =>\n props.icon === undefined ? [] : Array.isArray(props.icon) ? props.icon : [props.icon],\n)\n\nconst headerClasses = computed(() => [\n 'mint-section-card__header',\n props.divider === 'light' ? 'mint-section-card__header--divider-light' : '',\n hidden.value ? 'mint-section-card__header--collapsed' : '',\n])\n\nconst titleClasses = computed(() => [\n 'mint-section-card__title',\n props.uppercase ? 'mint-section-card__title--uppercase' : '',\n])\n\nconst cardClasses = computed(() => [\n 'mint-section-card',\n props.fill ? 'mint-section-card--fill' : '',\n hidden.value ? 'mint-section-card--collapsed' : '',\n])\n</script>\n\n<template>\n <section :class=\"cardClasses\">\n <div :class=\"headerClasses\">\n <span\n v-if=\"iconPaths.length\"\n :class=\"['mint-section-card__icon-chip', `mint-section-card__icon-chip--${accent}`]\"\n >\n <svg\n class=\"mint-section-card__icon\"\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 aria-hidden=\"true\"\n >\n <path v-for=\"(d, i) in iconPaths\" :key=\"i\" :d=\"d\" />\n </svg>\n </span>\n <!-- Disclosure pattern: the button sits inside the heading, never the reverse. -->\n <h2 :class=\"titleClasses\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"mint-section-card__toggle\"\n :aria-expanded=\"!collapsed\"\n :aria-controls=\"bodyId\"\n @click=\"collapsed = !collapsed\"\n >\n {{ title }}\n <span v-if=\"count !== undefined\" class=\"mint-section-card__count\">{{ count }}</span>\n <svg\n class=\"mint-section-card__chevron\"\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 aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <template v-else>{{ title }}</template>\n </h2>\n <span v-if=\"count !== undefined && !collapsible\" class=\"mint-section-card__count\">{{ count }}</span>\n <div class=\"mint-section-card__spacer\"></div>\n <slot name=\"actions\" />\n </div>\n <!-- Controls belong to the card's chrome, so they sit outside the body and\n stay put while the body scrolls. -->\n <div v-if=\"$slots.toolbar\" v-show=\"!hidden\" class=\"mint-section-card__toolbar\">\n <slot name=\"toolbar\" />\n </div>\n <div :id=\"bodyId\" v-show=\"!hidden\" class=\"mint-section-card__body\">\n <slot />\n </div>\n <div v-if=\"$slots.footer\" v-show=\"!hidden\" class=\"mint-section-card__footer\">\n <slot name=\"footer\" />\n </div>\n </section>\n</template>\n","<script setup lang=\"ts\">\n/** One-card page region from the MINT 1.1 card system: 12px radius, the\n * single --card-shadow token, a header strip (icon chip, title, mono count,\n * actions), and hairline-separated content — no card-in-card. */\nimport { computed, useId } from 'vue'\nimport type { SectionCardAccent, SectionCardDivider } from '../types'\n\ninterface Props {\n title: string\n /** Monospace meta shown next to the title, e.g. \"14 ongoing · 42 total\". */\n count?: string | number\n /** SVG path(s) for the 22px icon chip; omit to render no chip. */\n icon?: string | string[]\n /** Tint of the icon chip. */\n accent?: SectionCardAccent\n /** Uppercase category heading (home cards) vs plain heading (detail cards). */\n uppercase?: boolean\n /** Header hairline: 'strong' = --border-color, 'light' = --border-light. */\n divider?: SectionCardDivider\n /**\n * Stretch to fill the height its parent offers, scrolling the body and\n * pinning the header and footer. Requires a parent that gives the card a\n * definite height (a flex or grid track).\n */\n fill?: boolean\n /** The title toggles the body; bind `v-model:collapsed` to control or keep the state. */\n collapsible?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n count: undefined,\n icon: undefined,\n accent: 'primary',\n uppercase: false,\n divider: 'strong',\n fill: false,\n collapsible: false,\n})\n\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst bodyId = useId()\n/** `collapsed` hides content only on a card that also shows the toggle to open it again. */\nconst hidden = computed(() => props.collapsible && collapsed.value)\n\nconst iconPaths = computed(() =>\n props.icon === undefined ? [] : Array.isArray(props.icon) ? props.icon : [props.icon],\n)\n\nconst headerClasses = computed(() => [\n 'mint-section-card__header',\n props.divider === 'light' ? 'mint-section-card__header--divider-light' : '',\n hidden.value ? 'mint-section-card__header--collapsed' : '',\n])\n\nconst titleClasses = computed(() => [\n 'mint-section-card__title',\n props.uppercase ? 'mint-section-card__title--uppercase' : '',\n])\n\nconst cardClasses = computed(() => [\n 'mint-section-card',\n props.fill ? 'mint-section-card--fill' : '',\n hidden.value ? 'mint-section-card--collapsed' : '',\n])\n</script>\n\n<template>\n <section :class=\"cardClasses\">\n <div :class=\"headerClasses\">\n <span\n v-if=\"iconPaths.length\"\n :class=\"['mint-section-card__icon-chip', `mint-section-card__icon-chip--${accent}`]\"\n >\n <svg\n class=\"mint-section-card__icon\"\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 aria-hidden=\"true\"\n >\n <path v-for=\"(d, i) in iconPaths\" :key=\"i\" :d=\"d\" />\n </svg>\n </span>\n <!-- Disclosure pattern: the button sits inside the heading, never the reverse. -->\n <h2 :class=\"titleClasses\">\n <button\n v-if=\"collapsible\"\n type=\"button\"\n class=\"mint-section-card__toggle\"\n :aria-expanded=\"!collapsed\"\n :aria-controls=\"bodyId\"\n @click=\"collapsed = !collapsed\"\n >\n {{ title }}\n <span v-if=\"count !== undefined\" class=\"mint-section-card__count\">{{ count }}</span>\n <svg\n class=\"mint-section-card__chevron\"\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 aria-hidden=\"true\"\n >\n <path d=\"m6 9 6 6 6-6\" />\n </svg>\n </button>\n <template v-else>{{ title }}</template>\n </h2>\n <span v-if=\"count !== undefined && !collapsible\" class=\"mint-section-card__count\">{{ count }}</span>\n <div class=\"mint-section-card__spacer\"></div>\n <slot name=\"actions\" />\n </div>\n <!-- Controls belong to the card's chrome, so they sit outside the body and\n stay put while the body scrolls. -->\n <div v-if=\"$slots.toolbar\" v-show=\"!hidden\" class=\"mint-section-card__toolbar\">\n <slot name=\"toolbar\" />\n </div>\n <div :id=\"bodyId\" v-show=\"!hidden\" class=\"mint-section-card__body\">\n <slot />\n </div>\n <div v-if=\"$slots.footer\" v-show=\"!hidden\" class=\"mint-section-card__footer\">\n <slot name=\"footer\" />\n </div>\n </section>\n</template>\n","/**\n * Sample-type marker (SDK control refresh 3a): color encodes the group, shape\n * encodes the type.\n *\n * - sample: group fill (28% tint) + 1px group outline\n * - blank: dashed hollow ring, no group color\n * - qc: neutral solid ring + \"Q\"\n * - iqc: group fill + \"i\"\n *\n * `groupColor` must be a group token, `var(--mint-sample-1)` … `var(--mint-sample-8)`.\n * The caller owns geometry (size, radius, glyph font); this returns colors only.\n */\n\nexport type SampleMarkerType = 'sample' | 'blank' | 'qc' | 'iqc'\n\nexport interface SampleTypeMarker {\n style: { background: string; border: string; color: string }\n glyph: 'Q' | 'i' | null\n}\n\n/** `type` is matched case-insensitively; anything unrecognised renders as a sample. */\nexport function sampleTypeMarker(type: SampleMarkerType | (string & {}), groupColor: string): SampleTypeMarker {\n const groupFill = {\n background: `color-mix(in srgb, ${groupColor} 28%, var(--bg-card))`,\n border: `1px solid ${groupColor}`,\n color: 'var(--text-primary)',\n }\n switch (type.toLowerCase()) {\n case 'blank':\n return {\n style: { background: 'var(--bg-card)', border: '1.5px dashed var(--_control-border-strong)', color: 'var(--text-primary)' },\n glyph: null,\n }\n case 'qc':\n return {\n style: { background: 'var(--bg-card)', border: '1.5px solid var(--text-secondary-strong)', color: 'var(--text-primary)' },\n glyph: 'Q',\n }\n case 'iqc':\n return { style: groupFill, glyph: 'i' }\n default:\n return { style: groupFill, glyph: null }\n }\n}\n","import type { HeatmapConfig } from '../types'\n\n/** Color for the built-in sample types when `sampleColors` has no entry. */\nexport const DEFAULT_SAMPLE_TYPE_COLORS: Record<string, string> = {\n sample: '#10b981',\n control: '#3b82f6',\n blank: '#f97316',\n qc: '#8b5cf6',\n iqc: '#ec4899',\n}\n\nexport const HEATMAP_COLORS: Record<string, string[]> = {\n viridis: ['#440154', '#482878', '#3e4989', '#31688e', '#26828e', '#1f9e89', '#35b779', '#6ece58', '#b5de2b', '#fde725'],\n plasma: ['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921'],\n turbo: ['#30123b', '#4145ab', '#4675ed', '#39a2fc', '#1bcfd4', '#24e79e', '#71f05f', '#c1f034', '#f1c83c', '#f99538', '#e45a31', '#ba2512', '#7a0403'],\n}\n\nexport function conditionGradientStyle(\n color: string,\n concentration: number,\n concentrations: number[],\n): Record<string, string> {\n const min = Math.min(...concentrations)\n const max = Math.max(...concentrations)\n const t = max <= min ? 1 : (concentration - min) / (max - min)\n const opacity = 0.25 + t * 0.50\n const r = parseInt(color.slice(1, 3), 16)\n const g = parseInt(color.slice(3, 5), 16)\n const b = parseInt(color.slice(5, 7), 16)\n const er = 255 * (1 - opacity) + r * opacity\n const eg = 255 * (1 - opacity) + g * opacity\n const eb = 255 * (1 - opacity) + b * opacity\n const luminance = (0.299 * er + 0.587 * eg + 0.114 * eb) / 255\n return {\n backgroundColor: `rgba(${r}, ${g}, ${b}, ${opacity})`,\n color: luminance > 0.55 ? '#1e293b' : '#ffffff',\n }\n}\n\nexport function formatConcentration(value: number): string {\n if (value >= 1000) return `${value / 1000}k`\n if (value < 0.01) return value.toExponential(0)\n return String(value)\n}\n\nexport function getHeatmapColor(config: HeatmapConfig | undefined, value: number | undefined): string | null {\n if (!config?.enabled || value === undefined) return null\n\n const min = config.min ?? 0\n const max = config.max ?? 1\n const normalized = Math.max(0, Math.min(1, (value - min) / (max - min)))\n const colors = config.colorScale === 'custom' && config.customColors?.length\n ? config.customColors\n : HEATMAP_COLORS[config.colorScale || 'viridis']\n const index = Math.min(Math.floor(normalized * (colors.length - 1)), colors.length - 1)\n return colors[index]\n}\n","<script setup lang=\"ts\">\n/** Single-well inspector for WellPlate / PlateEditor, composed from SDK fields (3b). */\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { ChevronRight, X } from 'lucide-vue-next'\nimport type { Well, WellEditData, WellEditField } from '../../types'\nimport { useFloatingPanel } from '../../composables/useFloatingPanel'\nimport { useFocusTrap } from '../../composables/useFocusTrap'\nimport { sampleTypeMarker } from '../../utils/sampleTypeMarker'\nimport { DEFAULT_SAMPLE_TYPE_COLORS } from '../WellPlate.colors'\nimport BaseButton from '../BaseButton.vue'\nimport BaseInput from '../BaseInput.vue'\nimport FormField from '../FormField.vue'\nimport NumberInput from '../NumberInput.vue'\nimport SegmentedControl from '../SegmentedControl.vue'\n\ninterface Props {\n wellId: string\n wellData?: Partial<Well>\n editFields?: WellEditField[]\n defaultInjectionVolume?: number\n position: { x: number; y: number }\n /** Render in place (PlateEditor's rail) instead of as a teleported popover. */\n inline?: boolean\n /** Header marker color per group or well type (the plate's sampleColors); defaults by sample type. */\n markerColors?: Record<string, string>\n /** Name of the well's group, shown in the header in place of \"Edit well\". */\n groupLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n wellData: () => ({}),\n editFields: () => ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n inline: false,\n markerColors: () => ({}),\n groupLabel: undefined,\n})\n\nconst emit = defineEmits<{\n save: [data: WellEditData]\n clear: []\n close: []\n}>()\n\nconst TYPE_OPTIONS = [\n { value: 'sample', label: 'Sample' },\n { value: 'blank', label: 'Blank' },\n { value: 'qc', label: 'QC' },\n { value: 'iqc', label: 'iQC' },\n]\nconst INJECTION_OPTIONS = [1, 2, 3, 4, 5].map(n => ({ value: n, label: String(n) }))\n// Presets cover 1–5; more goes through the Advanced number field.\nconst MAX_PRESET_INJECTIONS = 5\n\n/** The payload last loaded or saved inline, so a commit only fires on a real change. */\nlet committed = ''\n\nconst formData = ref({\n label: '',\n sampleType: 'sample',\n injectionVolume: props.defaultInjectionVolume,\n injectionCount: 1,\n customMethod: '',\n})\nconst showAdvanced = ref(false)\n\nwatch(\n () => [props.wellId, props.wellData] as const,\n ([wellId], previous) => {\n const meta = props.wellData?.metadata as Record<string, unknown> | undefined\n formData.value = {\n label: (meta?.label as string) || '',\n sampleType: props.wellData?.sampleType || 'sample',\n injectionVolume: (meta?.injectionVolume as number) || props.defaultInjectionVolume,\n injectionCount: (meta?.injectionCount as number) || 1,\n customMethod: (meta?.customMethod as string) || '',\n }\n // An inline commit reloads the same well; only a new well resets the disclosure.\n if (wellId !== previous?.[0]) {\n showAdvanced.value = formData.value.injectionCount > MAX_PRESET_INJECTIONS || Boolean(formData.value.customMethod)\n }\n committed = JSON.stringify(payload())\n },\n { immediate: true },\n)\n\nconst fieldLayout = computed(() => (props.inline ? 'stacked' : 'row'))\nconst hasField = (field: WellEditField) => props.editFields.includes(field)\nconst hasAdvanced = computed(() => hasField('injectionCount') || hasField('customMethod'))\n\nconst marker = computed(() => {\n const { sampleType } = formData.value\n const group = props.wellData?.group\n return sampleTypeMarker(\n sampleType,\n (group ? props.markerColors[group] : undefined)\n ?? props.markerColors[sampleType] ?? DEFAULT_SAMPLE_TYPE_COLORS[sampleType] ?? 'var(--mint-sample-1)',\n )\n})\n\nfunction payload(): WellEditData {\n return {\n wellId: props.wellId,\n label: formData.value.label.trim(),\n sampleType: formData.value.sampleType,\n // An emptied number field reads null; fall back like the restore does.\n injectionVolume: Number(formData.value.injectionVolume) || props.defaultInjectionVolume,\n injectionCount: formData.value.injectionCount || 1,\n customMethod: formData.value.customMethod.trim(),\n }\n}\n\nfunction save() {\n emit('save', payload())\n}\n\n// Inline (the rail) there is no Save: segments apply on click, text fields on Enter or\n// when focus leaves the inspector. A move between its own fields is not a commit: an\n// emptied name would clear the well before the Type click that follows it.\nfunction commit() {\n if (!props.inline) return\n const next = JSON.stringify(payload())\n if (next === committed) return\n committed = next\n save()\n}\n\nfunction pick(field: 'sampleType' | 'injectionCount', value: string | number) {\n if (field === 'sampleType') formData.value.sampleType = String(value)\n else formData.value.injectionCount = Number(value)\n commit()\n}\n\nfunction handleFocusout(event: FocusEvent) {\n if (!containerRef.value?.contains(event.relatedTarget as Node | null)) commit()\n}\n\n// A Clear drops pending edits, so the focus that leaves with the closing rail commits none.\nfunction clearWell() {\n committed = JSON.stringify(payload())\n emit('clear')\n}\n\n/** An Enter that confirms an IME candidate is not a save. */\nconst isSaveEnter = (event: KeyboardEvent) => event.key === 'Enter' && !event.isComposing && event.keyCode !== 229\n\nfunction handleNameKeydown(event: KeyboardEvent) {\n if (isSaveEnter(event) && !props.inline) save()\n}\n\n// Enter in a text field ends the edit like the popover's Enter-save: commit, then close,\n// which hands focus back to the well.\nfunction handleInlineEnter(event: KeyboardEvent) {\n if (!isSaveEnter(event)) return\n if ((event.target as HTMLElement).tagName !== 'INPUT') return\n commit()\n emit('close')\n}\n\n// Placement at the clicked point: flip and clamp to the viewport. Inline, the\n// inspector stays in place and is not positioned.\nconst containerRef = ref<HTMLElement | null>(null)\nconst pointAnchor = () => {\n if (props.inline) return null\n const { x, y } = props.position\n return { getBoundingClientRect: () => ({ x, y, left: x, top: y, right: x, bottom: y, width: 0, height: 0 }) }\n}\nconst { style: panelStyle } = useFloatingPanel(pointAnchor, containerRef, computed(() => !props.inline), {\n placement: 'right-start',\n offset: 0,\n})\n\n// The parent mounts and unmounts the popup, so the trap follows the lifecycle,\n// the same way BaseModal drives it off `modelValue`.\nconst trapActive = ref(false)\nuseFocusTrap({ containerRef, active: trapActive })\n\n// Inline, the inspector sits beside the plate: no trap, but every well is a tab stop,\n// so focus moves to the first field for each well it opens on and back to the well on close.\nlet returnFocus: HTMLElement | null = null\nfunction focusInline() {\n const active = document.activeElement as HTMLElement | null\n if (!containerRef.value?.contains(active)) returnFocus = active\n containerRef.value\n ?.querySelector<HTMLElement>('.mint-well-edit-popup__body :is(input, button:not([tabindex=\"-1\"]))')\n ?.focus()\n}\nwatch(() => props.wellId, () => { if (props.inline) focusInline() }, { flush: 'post' })\n\nonMounted(() => {\n if (props.inline) return focusInline()\n trapActive.value = true\n // Runs after the trap's own first-element focus: start in the name field.\n void nextTick(() => containerRef.value?.querySelector<HTMLInputElement>('[data-field=\"label\"] input')?.focus())\n})\n\nonBeforeUnmount(() => {\n trapActive.value = false\n if (props.inline && containerRef.value?.contains(document.activeElement)) returnFocus?.focus()\n})\n</script>\n\n<template>\n <Teleport to=\"body\" :disabled=\"inline\">\n <div v-if=\"!inline\" class=\"mint-well-edit-popup__backdrop\" @click=\"emit('close')\" />\n\n <div\n ref=\"containerRef\"\n :class=\"inline ? 'mint-well-edit-popup mint-well-edit-popup--inline' : 'mint-popover mint-well-edit-popup'\"\n :style=\"inline ? undefined : panelStyle\"\n :role=\"inline ? 'region' : 'dialog'\"\n :aria-label=\"`Edit well ${wellId}`\"\n :tabindex=\"inline ? undefined : -1\"\n @click=\"inline || $event.stopPropagation()\"\n @keydown.escape.prevent=\"commit(); emit('close')\"\n @keydown=\"inline && handleInlineEnter($event)\"\n @focusout=\"handleFocusout\"\n >\n <div class=\"mint-well-edit-popup__header\">\n <span class=\"mint-well-edit-popup__marker\" :style=\"marker.style\" aria-hidden=\"true\">{{ marker.glyph }}</span>\n <span class=\"mint-well-edit-popup__well-id\">{{ wellId }}</span>\n <span class=\"mint-well-edit-popup__title\">{{ groupLabel ?? 'Edit well' }}</span>\n <BaseButton class=\"mint-well-edit-popup__close\" variant=\"ghost\" size=\"sm\" :icon=\"X\" label=\"Close\" @click=\"commit(); emit('close')\" />\n </div>\n\n <div class=\"mint-well-edit-popup__body\">\n <FormField v-if=\"hasField('label')\" label=\"Name\" :layout=\"fieldLayout\" data-field=\"label\">\n <BaseInput\n v-model=\"formData.label\"\n size=\"sm\"\n placeholder=\"Sample_001\"\n aria-label=\"Name\"\n @keydown=\"handleNameKeydown\"\n />\n </FormField>\n\n <FormField v-if=\"hasField('sampleType')\" label=\"Type\" :layout=\"fieldLayout\" data-field=\"sampleType\">\n <!-- No focus move on press, where the click does not focus (Safari): see handleFocusout. -->\n <SegmentedControl\n :model-value=\"formData.sampleType\"\n :options=\"TYPE_OPTIONS\"\n size=\"sm\"\n full-width\n aria-label=\"Type\"\n @mousedown.prevent\n @update:model-value=\"pick('sampleType', $event)\"\n />\n </FormField>\n\n <FormField v-if=\"hasField('injectionCount')\" label=\"Injections\" :layout=\"fieldLayout\" data-field=\"injectionCount\">\n <SegmentedControl\n :model-value=\"formData.injectionCount\"\n :options=\"INJECTION_OPTIONS\"\n size=\"sm\"\n full-width\n aria-label=\"Injections\"\n @mousedown.prevent\n @update:model-value=\"pick('injectionCount', $event)\"\n />\n </FormField>\n\n <FormField v-if=\"hasField('injectionVolume')\" label=\"Volume\" :layout=\"fieldLayout\" data-field=\"injectionVolume\">\n <NumberInput\n v-model=\"formData.injectionVolume\"\n size=\"sm\"\n :min=\"0.1\"\n :max=\"100\"\n :step=\"0.5\"\n unit=\"µL\"\n aria-label=\"Volume\"\n />\n </FormField>\n\n <template v-if=\"hasAdvanced\">\n <button\n type=\"button\"\n class=\"mint-well-edit-popup__disclosure\"\n :aria-expanded=\"showAdvanced\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <ChevronRight :size=\"14\" aria-hidden=\"true\" class=\"mint-well-edit-popup__chevron\" />\n Advanced\n </button>\n <div v-if=\"showAdvanced\" class=\"mint-well-edit-popup__advanced\">\n <FormField v-if=\"hasField('injectionCount')\" label=\"Injections\" :layout=\"fieldLayout\" hint=\"More than 5\">\n <NumberInput v-model=\"formData.injectionCount\" :min=\"1\" :max=\"10\" size=\"sm\" aria-label=\"Injections\" />\n </FormField>\n <FormField v-if=\"hasField('customMethod')\" label=\"Method\" :layout=\"fieldLayout\" hint=\"Empty uses the plate method\">\n <BaseInput v-model=\"formData.customMethod\" size=\"sm\" class=\"mint-well-edit-popup__mono\" aria-label=\"Method\" />\n </FormField>\n </div>\n </template>\n </div>\n\n <div class=\"mint-well-edit-popup__footer\">\n <!-- No focus move on press: an inline focusout commit would split the clear into two history steps. -->\n <BaseButton variant=\"ghost\" size=\"sm\" data-action=\"clear\" @mousedown.prevent @click=\"clearWell\">Clear well</BaseButton>\n <BaseButton v-if=\"!inline\" variant=\"primary\" size=\"sm\" data-action=\"save\" @click=\"save\">Save</BaseButton>\n </div>\n </div>\n </Teleport>\n</template>\n","<script setup lang=\"ts\">\n/** Single-well inspector for WellPlate / PlateEditor, composed from SDK fields (3b). */\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { ChevronRight, X } from 'lucide-vue-next'\nimport type { Well, WellEditData, WellEditField } from '../../types'\nimport { useFloatingPanel } from '../../composables/useFloatingPanel'\nimport { useFocusTrap } from '../../composables/useFocusTrap'\nimport { sampleTypeMarker } from '../../utils/sampleTypeMarker'\nimport { DEFAULT_SAMPLE_TYPE_COLORS } from '../WellPlate.colors'\nimport BaseButton from '../BaseButton.vue'\nimport BaseInput from '../BaseInput.vue'\nimport FormField from '../FormField.vue'\nimport NumberInput from '../NumberInput.vue'\nimport SegmentedControl from '../SegmentedControl.vue'\n\ninterface Props {\n wellId: string\n wellData?: Partial<Well>\n editFields?: WellEditField[]\n defaultInjectionVolume?: number\n position: { x: number; y: number }\n /** Render in place (PlateEditor's rail) instead of as a teleported popover. */\n inline?: boolean\n /** Header marker color per group or well type (the plate's sampleColors); defaults by sample type. */\n markerColors?: Record<string, string>\n /** Name of the well's group, shown in the header in place of \"Edit well\". */\n groupLabel?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n wellData: () => ({}),\n editFields: () => ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n inline: false,\n markerColors: () => ({}),\n groupLabel: undefined,\n})\n\nconst emit = defineEmits<{\n save: [data: WellEditData]\n clear: []\n close: []\n}>()\n\nconst TYPE_OPTIONS = [\n { value: 'sample', label: 'Sample' },\n { value: 'blank', label: 'Blank' },\n { value: 'qc', label: 'QC' },\n { value: 'iqc', label: 'iQC' },\n]\nconst INJECTION_OPTIONS = [1, 2, 3, 4, 5].map(n => ({ value: n, label: String(n) }))\n// Presets cover 1–5; more goes through the Advanced number field.\nconst MAX_PRESET_INJECTIONS = 5\n\n/** The payload last loaded or saved inline, so a commit only fires on a real change. */\nlet committed = ''\n\nconst formData = ref({\n label: '',\n sampleType: 'sample',\n injectionVolume: props.defaultInjectionVolume,\n injectionCount: 1,\n customMethod: '',\n})\nconst showAdvanced = ref(false)\n\nwatch(\n () => [props.wellId, props.wellData] as const,\n ([wellId], previous) => {\n const meta = props.wellData?.metadata as Record<string, unknown> | undefined\n formData.value = {\n label: (meta?.label as string) || '',\n sampleType: props.wellData?.sampleType || 'sample',\n injectionVolume: (meta?.injectionVolume as number) || props.defaultInjectionVolume,\n injectionCount: (meta?.injectionCount as number) || 1,\n customMethod: (meta?.customMethod as string) || '',\n }\n // An inline commit reloads the same well; only a new well resets the disclosure.\n if (wellId !== previous?.[0]) {\n showAdvanced.value = formData.value.injectionCount > MAX_PRESET_INJECTIONS || Boolean(formData.value.customMethod)\n }\n committed = JSON.stringify(payload())\n },\n { immediate: true },\n)\n\nconst fieldLayout = computed(() => (props.inline ? 'stacked' : 'row'))\nconst hasField = (field: WellEditField) => props.editFields.includes(field)\nconst hasAdvanced = computed(() => hasField('injectionCount') || hasField('customMethod'))\n\nconst marker = computed(() => {\n const { sampleType } = formData.value\n const group = props.wellData?.group\n return sampleTypeMarker(\n sampleType,\n (group ? props.markerColors[group] : undefined)\n ?? props.markerColors[sampleType] ?? DEFAULT_SAMPLE_TYPE_COLORS[sampleType] ?? 'var(--mint-sample-1)',\n )\n})\n\nfunction payload(): WellEditData {\n return {\n wellId: props.wellId,\n label: formData.value.label.trim(),\n sampleType: formData.value.sampleType,\n // An emptied number field reads null; fall back like the restore does.\n injectionVolume: Number(formData.value.injectionVolume) || props.defaultInjectionVolume,\n injectionCount: formData.value.injectionCount || 1,\n customMethod: formData.value.customMethod.trim(),\n }\n}\n\nfunction save() {\n emit('save', payload())\n}\n\n// Inline (the rail) there is no Save: segments apply on click, text fields on Enter or\n// when focus leaves the inspector. A move between its own fields is not a commit: an\n// emptied name would clear the well before the Type click that follows it.\nfunction commit() {\n if (!props.inline) return\n const next = JSON.stringify(payload())\n if (next === committed) return\n committed = next\n save()\n}\n\nfunction pick(field: 'sampleType' | 'injectionCount', value: string | number) {\n if (field === 'sampleType') formData.value.sampleType = String(value)\n else formData.value.injectionCount = Number(value)\n commit()\n}\n\nfunction handleFocusout(event: FocusEvent) {\n if (!containerRef.value?.contains(event.relatedTarget as Node | null)) commit()\n}\n\n// A Clear drops pending edits, so the focus that leaves with the closing rail commits none.\nfunction clearWell() {\n committed = JSON.stringify(payload())\n emit('clear')\n}\n\n/** An Enter that confirms an IME candidate is not a save. */\nconst isSaveEnter = (event: KeyboardEvent) => event.key === 'Enter' && !event.isComposing && event.keyCode !== 229\n\nfunction handleNameKeydown(event: KeyboardEvent) {\n if (isSaveEnter(event) && !props.inline) save()\n}\n\n// Enter in a text field ends the edit like the popover's Enter-save: commit, then close,\n// which hands focus back to the well.\nfunction handleInlineEnter(event: KeyboardEvent) {\n if (!isSaveEnter(event)) return\n if ((event.target as HTMLElement).tagName !== 'INPUT') return\n commit()\n emit('close')\n}\n\n// Placement at the clicked point: flip and clamp to the viewport. Inline, the\n// inspector stays in place and is not positioned.\nconst containerRef = ref<HTMLElement | null>(null)\nconst pointAnchor = () => {\n if (props.inline) return null\n const { x, y } = props.position\n return { getBoundingClientRect: () => ({ x, y, left: x, top: y, right: x, bottom: y, width: 0, height: 0 }) }\n}\nconst { style: panelStyle } = useFloatingPanel(pointAnchor, containerRef, computed(() => !props.inline), {\n placement: 'right-start',\n offset: 0,\n})\n\n// The parent mounts and unmounts the popup, so the trap follows the lifecycle,\n// the same way BaseModal drives it off `modelValue`.\nconst trapActive = ref(false)\nuseFocusTrap({ containerRef, active: trapActive })\n\n// Inline, the inspector sits beside the plate: no trap, but every well is a tab stop,\n// so focus moves to the first field for each well it opens on and back to the well on close.\nlet returnFocus: HTMLElement | null = null\nfunction focusInline() {\n const active = document.activeElement as HTMLElement | null\n if (!containerRef.value?.contains(active)) returnFocus = active\n containerRef.value\n ?.querySelector<HTMLElement>('.mint-well-edit-popup__body :is(input, button:not([tabindex=\"-1\"]))')\n ?.focus()\n}\nwatch(() => props.wellId, () => { if (props.inline) focusInline() }, { flush: 'post' })\n\nonMounted(() => {\n if (props.inline) return focusInline()\n trapActive.value = true\n // Runs after the trap's own first-element focus: start in the name field.\n void nextTick(() => containerRef.value?.querySelector<HTMLInputElement>('[data-field=\"label\"] input')?.focus())\n})\n\nonBeforeUnmount(() => {\n trapActive.value = false\n if (props.inline && containerRef.value?.contains(document.activeElement)) returnFocus?.focus()\n})\n</script>\n\n<template>\n <Teleport to=\"body\" :disabled=\"inline\">\n <div v-if=\"!inline\" class=\"mint-well-edit-popup__backdrop\" @click=\"emit('close')\" />\n\n <div\n ref=\"containerRef\"\n :class=\"inline ? 'mint-well-edit-popup mint-well-edit-popup--inline' : 'mint-popover mint-well-edit-popup'\"\n :style=\"inline ? undefined : panelStyle\"\n :role=\"inline ? 'region' : 'dialog'\"\n :aria-label=\"`Edit well ${wellId}`\"\n :tabindex=\"inline ? undefined : -1\"\n @click=\"inline || $event.stopPropagation()\"\n @keydown.escape.prevent=\"commit(); emit('close')\"\n @keydown=\"inline && handleInlineEnter($event)\"\n @focusout=\"handleFocusout\"\n >\n <div class=\"mint-well-edit-popup__header\">\n <span class=\"mint-well-edit-popup__marker\" :style=\"marker.style\" aria-hidden=\"true\">{{ marker.glyph }}</span>\n <span class=\"mint-well-edit-popup__well-id\">{{ wellId }}</span>\n <span class=\"mint-well-edit-popup__title\">{{ groupLabel ?? 'Edit well' }}</span>\n <BaseButton class=\"mint-well-edit-popup__close\" variant=\"ghost\" size=\"sm\" :icon=\"X\" label=\"Close\" @click=\"commit(); emit('close')\" />\n </div>\n\n <div class=\"mint-well-edit-popup__body\">\n <FormField v-if=\"hasField('label')\" label=\"Name\" :layout=\"fieldLayout\" data-field=\"label\">\n <BaseInput\n v-model=\"formData.label\"\n size=\"sm\"\n placeholder=\"Sample_001\"\n aria-label=\"Name\"\n @keydown=\"handleNameKeydown\"\n />\n </FormField>\n\n <FormField v-if=\"hasField('sampleType')\" label=\"Type\" :layout=\"fieldLayout\" data-field=\"sampleType\">\n <!-- No focus move on press, where the click does not focus (Safari): see handleFocusout. -->\n <SegmentedControl\n :model-value=\"formData.sampleType\"\n :options=\"TYPE_OPTIONS\"\n size=\"sm\"\n full-width\n aria-label=\"Type\"\n @mousedown.prevent\n @update:model-value=\"pick('sampleType', $event)\"\n />\n </FormField>\n\n <FormField v-if=\"hasField('injectionCount')\" label=\"Injections\" :layout=\"fieldLayout\" data-field=\"injectionCount\">\n <SegmentedControl\n :model-value=\"formData.injectionCount\"\n :options=\"INJECTION_OPTIONS\"\n size=\"sm\"\n full-width\n aria-label=\"Injections\"\n @mousedown.prevent\n @update:model-value=\"pick('injectionCount', $event)\"\n />\n </FormField>\n\n <FormField v-if=\"hasField('injectionVolume')\" label=\"Volume\" :layout=\"fieldLayout\" data-field=\"injectionVolume\">\n <NumberInput\n v-model=\"formData.injectionVolume\"\n size=\"sm\"\n :min=\"0.1\"\n :max=\"100\"\n :step=\"0.5\"\n unit=\"µL\"\n aria-label=\"Volume\"\n />\n </FormField>\n\n <template v-if=\"hasAdvanced\">\n <button\n type=\"button\"\n class=\"mint-well-edit-popup__disclosure\"\n :aria-expanded=\"showAdvanced\"\n @click=\"showAdvanced = !showAdvanced\"\n >\n <ChevronRight :size=\"14\" aria-hidden=\"true\" class=\"mint-well-edit-popup__chevron\" />\n Advanced\n </button>\n <div v-if=\"showAdvanced\" class=\"mint-well-edit-popup__advanced\">\n <FormField v-if=\"hasField('injectionCount')\" label=\"Injections\" :layout=\"fieldLayout\" hint=\"More than 5\">\n <NumberInput v-model=\"formData.injectionCount\" :min=\"1\" :max=\"10\" size=\"sm\" aria-label=\"Injections\" />\n </FormField>\n <FormField v-if=\"hasField('customMethod')\" label=\"Method\" :layout=\"fieldLayout\" hint=\"Empty uses the plate method\">\n <BaseInput v-model=\"formData.customMethod\" size=\"sm\" class=\"mint-well-edit-popup__mono\" aria-label=\"Method\" />\n </FormField>\n </div>\n </template>\n </div>\n\n <div class=\"mint-well-edit-popup__footer\">\n <!-- No focus move on press: an inline focusout commit would split the clear into two history steps. -->\n <BaseButton variant=\"ghost\" size=\"sm\" data-action=\"clear\" @mousedown.prevent @click=\"clearWell\">Clear well</BaseButton>\n <BaseButton v-if=\"!inline\" variant=\"primary\" size=\"sm\" data-action=\"save\" @click=\"save\">Save</BaseButton>\n </div>\n </div>\n </Teleport>\n</template>\n","import type { ColumnCondition, RowCondition } from '../types'\n\nexport interface ConditionEntry<TCondition> {\n condition: TCondition\n indexInGroup: number\n}\n\nexport type ColumnConditionSpan =\n | { condition: ColumnCondition; colspan: number }\n | { gap: true; colspan: number }\n\nexport type RowConditionSpan =\n | { condition: RowCondition; rowspan: number; startRow: number }\n | { gap: true; rowspan: number; startRow: number }\n\nexport function createColumnConditionMap(\n conditions: ColumnCondition[],\n): Map<number, ConditionEntry<ColumnCondition>> {\n const map = new Map<number, ConditionEntry<ColumnCondition>>()\n for (const condition of conditions) {\n condition.cols.forEach((col, indexInGroup) => {\n map.set(col, { condition, indexInGroup })\n })\n }\n return map\n}\n\nexport function createRowConditionMap(\n conditions: RowCondition[],\n): Map<string, ConditionEntry<RowCondition>> {\n const map = new Map<string, ConditionEntry<RowCondition>>()\n for (const condition of conditions) {\n condition.rows.forEach((row, indexInGroup) => {\n map.set(row, { condition, indexInGroup })\n })\n }\n return map\n}\n\nexport function createColumnConditionSpans(\n cols: number[],\n conditionMap: ReadonlyMap<number, ConditionEntry<ColumnCondition>>,\n): ColumnConditionSpan[] {\n const spans: ColumnConditionSpan[] = []\n let index = 0\n\n while (index < cols.length) {\n const entry = conditionMap.get(cols[index])\n if (entry?.indexInGroup === 0) {\n spans.push({ condition: entry.condition, colspan: entry.condition.cols.length })\n index += entry.condition.cols.length\n continue\n }\n\n let gapCount = 0\n while (index + gapCount < cols.length && !conditionMap.has(cols[index + gapCount])) {\n gapCount++\n }\n spans.push({ gap: true, colspan: gapCount || 1 })\n index += gapCount || 1\n }\n\n return spans\n}\n\nexport function createRowConditionSpans(\n rows: string[],\n conditionMap: ReadonlyMap<string, ConditionEntry<RowCondition>>,\n): RowConditionSpan[] {\n const spans: RowConditionSpan[] = []\n let index = 0\n\n while (index < rows.length) {\n const entry = conditionMap.get(rows[index])\n if (entry?.indexInGroup === 0) {\n spans.push({ condition: entry.condition, rowspan: entry.condition.rows.length, startRow: index })\n index += entry.condition.rows.length\n continue\n }\n\n let gapCount = 0\n while (index + gapCount < rows.length && !conditionMap.has(rows[index + gapCount])) {\n gapCount++\n }\n spans.push({ gap: true, rowspan: gapCount || 1, startRow: index })\n index += gapCount || 1\n }\n\n return spans\n}\n\nexport function createRowConditionSpanByRow(\n spans: RowConditionSpan[],\n): Map<number, RowConditionSpan> {\n const map = new Map<number, RowConditionSpan>()\n for (const span of spans) {\n map.set(span.startRow, span)\n }\n return map\n}\n","import type { Well, WellPlateFormat, WellPlateSize } from '../types'\n\nexport interface WellPlateConfig {\n rows: number\n cols: number\n}\n\nexport interface WellPlateCoordinate {\n row: number\n col: number\n}\n\nexport interface WellPlateSizeConfig {\n cellWidth: string\n cellHeight: string\n headerWidth: string\n headerHeight: string\n fontSize: string\n gap: string\n}\n\nexport const PLATE_CONFIGS: Record<WellPlateFormat, WellPlateConfig> = {\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\nexport const WELL_PLATE_SIZE_CONFIGS: Record<WellPlateSize, WellPlateSizeConfig> = {\n // Compact square wells (PlateEditor, 7a): room for the Q / i glyph, not a sample name.\n xs: { cellWidth: '26px', cellHeight: '26px', headerWidth: '20px', headerHeight: '20px', fontSize: '0.6875rem', gap: '6px' },\n sm: { cellWidth: '40px', cellHeight: '40px', headerWidth: '32px', headerHeight: '32px', fontSize: '0.625rem', gap: '2px' },\n md: { cellWidth: '56px', cellHeight: '32px', headerWidth: '32px', headerHeight: '24px', fontSize: '0.75rem', gap: '3px' },\n lg: { cellWidth: '80px', cellHeight: '40px', headerWidth: '40px', headerHeight: '32px', fontSize: '0.875rem', gap: '4px' },\n xl: { cellWidth: '96px', cellHeight: '48px', headerWidth: '48px', headerHeight: '40px', fontSize: '1rem', gap: '4px' },\n fill: { cellWidth: '100%', cellHeight: '32px', headerWidth: '32px', headerHeight: '24px', fontSize: '0.75rem', gap: '2px' },\n}\n\n/**\n * Spreadsheet-style row label for a zero-based row index: A…Z, then AA, AB, …\n *\n * This is the single row-label generator for the SDK — plate grids, rack\n * grids, well-id parsing and the template builders all go through it.\n */\nexport function rowLabel(index: number): string {\n let label = ''\n let current = index\n for (;;) {\n label = String.fromCharCode(65 + (current % 26)) + label\n current = Math.floor(current / 26) - 1\n if (current < 0) return label\n }\n}\n\n/** Inverse of {@link rowLabel}: 'A' → 0, 'Z' → 25, 'AA' → 26. */\nexport function rowIndex(label: string): number {\n return [...label].reduce((value, char) => value * 26 + char.charCodeAt(0) - 64, 0) - 1\n}\n\nexport function createPlateRowLabels(config: WellPlateConfig): string[] {\n return Array.from({ length: config.rows }, (_, index) => rowLabel(index))\n}\n\nexport function createPlateColumnLabels(config: WellPlateConfig): number[] {\n return Array.from({ length: config.cols }, (_, index) => index + 1)\n}\n\nexport function createWellGrid(\n config: WellPlateConfig,\n rowLabels: string[],\n wells: Record<string, Partial<Well>>,\n): Well[][] {\n const grid: Well[][] = []\n for (let row = 0; row < config.rows; row++) {\n const rowWells: Well[] = []\n for (let col = 0; col < config.cols; col++) {\n const id = `${rowLabels[row]}${col + 1}`\n const wellData = wells[id] || {}\n rowWells.push({\n id,\n row,\n col,\n state: wellData.state || 'empty',\n sampleType: wellData.sampleType,\n group: wellData.group,\n value: wellData.value,\n metadata: wellData.metadata,\n })\n }\n grid.push(rowWells)\n }\n return grid\n}\n\nexport function getRectangleWellIds(\n start: WellPlateCoordinate,\n end: WellPlateCoordinate,\n rowLabels: string[],\n): Set<string> {\n const minRow = Math.min(start.row, end.row)\n const maxRow = Math.max(start.row, end.row)\n const minCol = Math.min(start.col, end.col)\n const maxCol = Math.max(start.col, end.col)\n\n const wells = new Set<string>()\n for (let row = minRow; row <= maxRow; row++) {\n for (let col = minCol; col <= maxCol; col++) {\n wells.add(`${rowLabels[row]}${col + 1}`)\n }\n }\n return wells\n}\n","import type { WellSampleDropData } from '../types'\n\ninterface DroppedSampleTransfer {\n getData(format: string): string\n}\n\nexport function parseDroppedSampleTransfer(transfer: DroppedSampleTransfer): WellSampleDropData | null {\n if (jsonPayload(transfer)) return parseDroppedSampleJson(transfer)\n return createTextDroppedSample(transfer.getData('text/plain'))\n}\n\n/** JSON payloads only — no `text/plain` fallback, so stray text drags are rejected. */\nexport function parseDroppedSampleJson(transfer: DroppedSampleTransfer): WellSampleDropData | null {\n const json = jsonPayload(transfer)\n if (!json) return null\n try {\n return normalizeDroppedSample(JSON.parse(json))\n } catch {\n return null\n }\n}\n\nfunction jsonPayload(transfer: DroppedSampleTransfer): string {\n return transfer.getData('application/json') || transfer.getData('text/json')\n}\n\nexport function createTextDroppedSample(text: string | null | undefined): WellSampleDropData | null {\n const sampleName = text?.trim()\n if (!sampleName) return null\n\n return {\n sampleName,\n label: sampleName,\n raw: sampleName,\n }\n}\n\nexport function normalizeDroppedSample(raw: unknown): WellSampleDropData | null {\n if (!raw || typeof raw !== 'object') return null\n\n const record = raw as Record<string, unknown>\n const sampleName =\n getString(record.sampleName) ??\n getString(record.sample_name) ??\n getString(record.name) ??\n getString(record.label)\n const label = getString(record.label) ?? sampleName\n const sampleType =\n getString(record.sampleType) ??\n getString(record.sample_type) ??\n (record.type === 'sample' ? 'sample' : undefined)\n\n if (!sampleName && !label && !getString(record.id)) return null\n\n return {\n id: getString(record.id),\n sampleName,\n label,\n sampleType,\n injectionVolume: getPositive(record.injectionVolume ?? record.injection_volume),\n injectionCount: getPositive(record.injectionCount ?? record.injection_count),\n customMethod: getString(record.customMethod ?? record.custom_method) ?? null,\n metadata: getRecord(record.metadata),\n raw,\n }\n}\n\nfunction getString(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim() ? value.trim() : undefined\n}\n\n/** Volumes and counts from an untrusted drag payload: zero or less is dropped. */\nfunction getPositive(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : undefined\n}\n\nfunction getRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === 'object' && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined\n}\n","import { computed, ref, watch } from 'vue'\nimport type {\n Well,\n WellEditData,\n WellPlateSelectionMode,\n WellSampleDropData,\n WellSampleDropParser,\n} from '../types'\nimport {\n getRectangleWellIds,\n type WellPlateCoordinate,\n} from './WellPlate.geometry'\nimport { parseDroppedSampleTransfer } from './WellPlate.sampleDrop'\nimport { useReorderAnnouncer } from '../composables/useListReorder'\n\nexport interface WellPlateInteractionOptions {\n modelValue: () => string[]\n selectionMode: () => WellPlateSelectionMode\n disabled: () => boolean\n readonly: () => boolean\n editable: () => boolean\n allowSampleDrop: () => boolean\n sampleDropParser: () => WellSampleDropParser | undefined\n rowLabels: () => string[]\n wellGrid: () => Well[][]\n emitSelected: (wellIds: string[]) => void\n emitWellClick: (wellId: string, event: MouseEvent) => void\n emitWellHover: (wellId: string | null, event?: MouseEvent) => void\n emitContextMenu: (wellId: string, event: MouseEvent) => void\n emitWellMove: (sourceWellId: string, targetWellId: string) => void\n emitWellEdit: (wellId: string, data: WellEditData) => void\n emitWellClear: (wellId: string) => void\n emitSampleDrop: (wellId: string, data: WellSampleDropData, event: DragEvent) => void\n}\n\nexport function nextClickedWellSelection(\n selectedWellIds: string[],\n wellId: string,\n selectionMode: WellPlateSelectionMode,\n event: Pick<MouseEvent, 'shiftKey' | 'metaKey' | 'ctrlKey'>,\n): string[] {\n const selectedWellSet = new Set(selectedWellIds)\n const isCurrentlySelected = selectedWellSet.has(wellId)\n const isMultiSelect = event.shiftKey || event.metaKey || event.ctrlKey\n\n if (selectionMode === 'single') {\n return isCurrentlySelected ? [] : [wellId]\n }\n\n if (isMultiSelect) {\n return isCurrentlySelected\n ? selectedWellIds.filter(id => id !== wellId)\n : [...selectedWellIds, wellId]\n }\n\n return isCurrentlySelected && selectedWellIds.length === 1 ? [] : [wellId]\n}\n\nexport function useWellPlateInteraction(options: WellPlateInteractionOptions) {\n const dragSourceWell = ref<string | null>(null)\n const dragTargetWell = ref<string | null>(null)\n const isDragging = ref(false)\n const dragStart = ref<WellPlateCoordinate | null>(null)\n const dragEnd = ref<WellPlateCoordinate | null>(null)\n const hoveredWell = ref<string | null>(null)\n const editingWellId = ref<string | null>(null)\n const editPopupPosition = ref({ x: 0, y: 0 })\n // A popover hidden by `editable` turning off stays closed when it turns back on.\n watch(options.editable, editable => { if (!editable) editingWellId.value = null })\n const { announcement, announce, announcerStyle } = useReorderAnnouncer()\n // Shift-click ranges from the last plain or Ctrl/Cmd-clicked well, or a dragged rectangle's\n // start; a cleared selection drops it.\n let anchorWellId: string | null = null\n let dragStartWellId: string | null = null\n watch(() => options.modelValue().length === 0, empty => { if (empty) anchorWellId = null })\n\n const selectedWellSet = computed(() => new Set(options.modelValue()))\n\n const dragSelectedWells = computed(() => {\n if (!isDragging.value || !dragStart.value || !dragEnd.value) return new Set<string>()\n return getRectangleWellIds(dragStart.value, dragEnd.value, options.rowLabels())\n })\n\n function emitSelected(wellIds: string[]) {\n options.emitSelected(wellIds)\n }\n\n function isSelected(wellId: string): boolean {\n return selectedWellSet.value.has(wellId) || dragSelectedWells.value.has(wellId)\n }\n\n function openEditPopup(wellId: string, event: MouseEvent) {\n const target = event.currentTarget as HTMLElement\n const rect = target.getBoundingClientRect()\n editPopupPosition.value = {\n x: rect.right + 8,\n y: rect.top,\n }\n editingWellId.value = wellId\n }\n\n function handleWellClick(well: Well, event: MouseEvent) {\n if (options.disabled()) return\n\n // Readonly forbids edits, not clicks: the caller may still react (e.g. open details).\n options.emitWellClick(well.id, event)\n if (options.readonly()) return\n\n const selectionMode = options.selectionMode()\n const isMultiSelect = event.shiftKey || event.metaKey || event.ctrlKey\n const canMultiSelect = selectionMode === 'multiple' || selectionMode === 'rectangle'\n\n // On an editable plate a plain click opens the editor; a modifier click still\n // toggles the selection so single-well editing and bulk actions coexist.\n if (options.editable() && !(isMultiSelect && canMultiSelect)) {\n anchorWellId = well.id\n openEditPopup(well.id, event)\n return\n }\n\n if (selectionMode === 'none') return\n\n // Looked up by id: a format change leaves no stale coordinates behind.\n const anchor = event.shiftKey && canMultiSelect\n ? options.wellGrid().flat().find(w => w.id === anchorWellId)\n : undefined\n if (anchor) {\n const range = getRectangleWellIds(anchor, well, options.rowLabels())\n // Ctrl/Cmd+Shift adds the range to the selection; Shift alone replaces it.\n const keep = event.ctrlKey || event.metaKey ? options.modelValue() : []\n emitSelected(Array.from(new Set([...keep, ...range])))\n return\n }\n\n anchorWellId = well.id\n emitSelected(nextClickedWellSelection(options.modelValue(), well.id, selectionMode, event))\n }\n\n function handleEditSave(data: WellEditData) {\n options.emitWellEdit(data.wellId, data)\n editingWellId.value = null\n }\n\n function handleEditClear() {\n if (editingWellId.value) options.emitWellClear(editingWellId.value)\n editingWellId.value = null\n }\n\n function handleEditClose() {\n editingWellId.value = null\n }\n\n function handleWellMouseDown(well: Well, event: MouseEvent) {\n if (options.disabled() || options.readonly() || options.selectionMode() !== 'rectangle') return\n if (options.editable()) return\n if (event.button !== 0) return\n\n isDragging.value = true\n dragStartWellId = well.id\n dragStart.value = { row: well.row, col: well.col }\n dragEnd.value = { row: well.row, col: well.col }\n }\n\n function handleWellMouseEnter(well: Well, event: MouseEvent) {\n hoveredWell.value = well.id\n options.emitWellHover(well.id, event)\n\n if (isDragging.value && options.selectionMode() === 'rectangle') {\n dragEnd.value = { row: well.row, col: well.col }\n }\n }\n\n function handleWellMouseLeave() {\n hoveredWell.value = null\n options.emitWellHover(null)\n }\n\n function handleMouseUp() {\n if (!isDragging.value || options.selectionMode() !== 'rectangle') return\n\n const newSelection = Array.from(dragSelectedWells.value)\n isDragging.value = false\n dragStart.value = null\n dragEnd.value = null\n\n // A one-well rectangle is a click: the click handler selects (or toggles) it.\n if (newSelection.length > 1) {\n anchorWellId = dragStartWellId\n emitSelected(newSelection)\n }\n }\n\n function handleContextMenu(well: Well, event: MouseEvent) {\n event.preventDefault()\n options.emitContextMenu(well.id, event)\n }\n\n function handleDragStart(well: Well, event: DragEvent) {\n if (options.disabled() || options.readonly() || options.selectionMode() !== 'drag') return\n if (!well.sampleType) return\n\n dragSourceWell.value = well.id\n if (event.dataTransfer) {\n event.dataTransfer.effectAllowed = 'move'\n event.dataTransfer.setData('text/plain', well.id)\n }\n }\n\n function handleDragOver(well: Well, event: DragEvent) {\n const isMovingWell = options.selectionMode() === 'drag' && !!dragSourceWell.value\n if (!isMovingWell && !options.allowSampleDrop()) return\n\n event.preventDefault()\n if (event.dataTransfer) {\n event.dataTransfer.dropEffect = isMovingWell ? 'move' : 'copy'\n }\n dragTargetWell.value = well.id\n }\n\n function handleDragLeave() {\n dragTargetWell.value = null\n }\n\n function handleDrop(well: Well, event: DragEvent) {\n const isMovingWell = options.selectionMode() === 'drag' && !!dragSourceWell.value\n if (!isMovingWell && !options.allowSampleDrop()) return\n\n event.preventDefault()\n\n if (isMovingWell && dragSourceWell.value) {\n const sourceId = dragSourceWell.value\n const targetId = well.id\n\n if (sourceId !== targetId) options.emitWellMove(sourceId, targetId)\n\n dragSourceWell.value = null\n dragTargetWell.value = null\n return\n }\n\n const droppedSample = parseDroppedSample(event, well.id)\n if (droppedSample) options.emitSampleDrop(well.id, droppedSample, event)\n\n dragSourceWell.value = null\n dragTargetWell.value = null\n }\n\n function handleDragEnd() {\n dragSourceWell.value = null\n dragTargetWell.value = null\n }\n\n // Keyboard alternative to the well-to-well drag. The plate is a 2D grid, not a\n // list, so it cannot use `useListReorder`'s index maths: instead Alt+Enter (or\n // Alt+Space) picks a filled well up, the same chord on another well drops it\n // there, and Escape cancels. Every step is announced politely.\n function handleWellMoveKeydown(well: Well, event: KeyboardEvent) {\n if (options.disabled() || options.readonly() || options.selectionMode() !== 'drag') return\n\n if (event.key === 'Escape' && dragSourceWell.value) {\n event.preventDefault()\n announce(`Move cancelled. ${dragSourceWell.value} stays in place.`)\n dragSourceWell.value = null\n dragTargetWell.value = null\n return\n }\n\n if (!event.altKey || (event.key !== 'Enter' && event.key !== ' ')) return\n event.preventDefault()\n\n const source = dragSourceWell.value\n if (!source) {\n if (!well.sampleType) {\n announce(`Well ${well.id} is empty and cannot be moved.`)\n return\n }\n dragSourceWell.value = well.id\n announce(`Well ${well.id} picked up. Move to another well and press Alt and Enter to drop it.`)\n return\n }\n\n if (source !== well.id) {\n options.emitWellMove(source, well.id)\n announce(`Well ${source} moved to ${well.id}.`)\n } else {\n announce(`Move cancelled. ${source} stays in place.`)\n }\n dragSourceWell.value = null\n dragTargetWell.value = null\n }\n\n function parseDroppedSample(event: DragEvent, wellId: string): WellSampleDropData | null {\n const customParser = options.sampleDropParser()\n if (customParser) return customParser(event, { wellId, event }) ?? null\n\n const transfer = event.dataTransfer\n if (!transfer) return null\n return parseDroppedSampleTransfer(transfer)\n }\n\n function handleKeyDown(event: KeyboardEvent) {\n if (options.disabled() || options.readonly()) return\n\n if (event.key === 'Escape') {\n if (editingWellId.value) {\n editingWellId.value = null\n return\n }\n anchorWellId = null\n emitSelected([])\n }\n\n if ((event.key === 'a' || event.key === 'A') && (event.metaKey || event.ctrlKey)) {\n event.preventDefault()\n emitSelected(options.wellGrid().flat().map(w => w.id))\n }\n }\n\n return {\n dragSourceWell,\n dragTargetWell,\n isDragging,\n dragStart,\n dragEnd,\n hoveredWell,\n editingWellId,\n editPopupPosition,\n selectedWellSet,\n dragSelectedWells,\n isSelected,\n handleWellClick,\n handleEditSave,\n handleEditClear,\n handleEditClose,\n handleWellMouseDown,\n handleWellMouseEnter,\n handleWellMouseLeave,\n handleMouseUp,\n handleContextMenu,\n handleDragStart,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleDragEnd,\n handleWellMoveKeydown,\n handleKeyDown,\n moveAnnouncement: announcement,\n moveAnnouncerStyle: announcerStyle,\n }\n}\n","import type { HeatmapConfig, Well, WellPlateSelectionMode, WellShape } from '../types'\nimport {\n DEFAULT_SAMPLE_TYPE_COLORS,\n getHeatmapColor,\n} from './WellPlate.colors'\n\nexport interface WellClassState {\n wellShape: WellShape\n selectionMode: WellPlateSelectionMode\n selected: boolean\n dragOver: boolean\n hovered: boolean\n disabled: boolean\n readonly: boolean\n dragSource: boolean\n dragTarget: boolean\n}\n\nexport interface WellStyleState {\n heatmap: HeatmapConfig\n sampleColors: Record<string, string>\n}\n\nexport function wellClasses(well: Well, state: WellClassState): string[] {\n const classes = [\n 'mint-well-plate__well',\n state.wellShape === 'circle' ? 'mint-well-plate__well--circle' : 'mint-well-plate__well--rounded',\n ]\n\n if (state.selectionMode === 'drag' && well.sampleType) {\n classes.push('mint-well-plate__well--draggable')\n }\n\n if (state.dragSource) classes.push('mint-well-plate__well--drag-source')\n else if (state.dragTarget) classes.push('mint-well-plate__well--drag-target')\n else if (state.selected) classes.push('mint-well-plate__well--selected')\n else if (state.dragOver) classes.push('mint-well-plate__well--drag-over')\n else if (state.hovered && !state.readonly) classes.push('mint-well-plate__well--hovered')\n\n if (state.disabled) classes.push('mint-well-plate__well--disabled')\n if (well.state === 'filled') classes.push('mint-well-plate__well--filled')\n\n return classes\n}\n\nexport function wellStyle(well: Well, state: WellStyleState): Record<string, string> {\n const heatmapColor = getHeatmapColor(state.heatmap, well.value)\n if (heatmapColor) {\n return { backgroundColor: heatmapColor, border: '1px solid transparent' }\n }\n\n const groupColor = well.sampleType\n && ((well.group ? state.sampleColors[well.group] : undefined)\n ?? state.sampleColors[well.sampleType] ?? DEFAULT_SAMPLE_TYPE_COLORS[well.sampleType])\n if (!groupColor) return {} // empty / filled wells are styled by well-plate.css\n\n return sampleTint(groupColor)\n}\n\n/** 15% fill and 40% outline of a sample color; color-mix accepts hex and `var()` tokens alike. */\nexport function sampleTint(color: string): { backgroundColor: string; border: string } {\n return {\n backgroundColor: `color-mix(in srgb, ${color} 15%, transparent)`,\n border: `1px solid color-mix(in srgb, ${color} 40%, transparent)`,\n }\n}\n\nexport function wellLabel(well: Well, showWellLabels: boolean): string | undefined {\n if (!showWellLabels) return undefined\n return well.metadata?.label as string | undefined\n}\n\nexport function wellBadge(\n well: Well,\n showBadges: boolean,\n): { text: string; color: string } | null {\n if (!showBadges || !well.metadata) return null\n const count = well.metadata.injectionCount as number | undefined\n if (count && count > 1) {\n return { text: String(count), color: '#6366f1' }\n }\n if (well.metadata.customMethod) {\n return { text: '+', color: '#ec4899' }\n }\n return null\n}\n","import type { WellLegendItem } from '../types'\n\nexport const DEFAULT_WELL_LEGEND_ITEMS: WellLegendItem[] = [\n { type: 'sample', label: 'Sample', color: '#10b981' },\n { type: 'blank', label: 'Blank', color: '#f97316' },\n { type: 'qc', label: 'QC', color: '#8b5cf6' },\n { type: 'iqc', label: 'IQC', color: '#ec4899' },\n]\n","<script setup lang=\"ts\">\n/** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */\nimport { computed } from 'vue'\nimport type { Well, WellEditField, WellEditData, WellSampleDropData, WellClassValue, WellPlateSpec } from '../types'\nimport { applySpecDefaults } from '../composables/componentSpecs'\nimport { useEventListener } from '../composables/useEventListener'\nimport WellEditPopupInternal from './internal/WellEditPopupInternal.vue'\nimport {\n conditionGradientStyle,\n formatConcentration as formatConc,\n HEATMAP_COLORS,\n} from './WellPlate.colors'\nimport {\n createColumnConditionMap,\n createColumnConditionSpans,\n createRowConditionMap,\n createRowConditionSpanByRow,\n createRowConditionSpans,\n} from './WellPlate.conditions'\nimport {\n createPlateColumnLabels,\n createPlateRowLabels,\n createWellGrid,\n PLATE_CONFIGS,\n WELL_PLATE_SIZE_CONFIGS,\n} from './WellPlate.geometry'\nimport { useWellPlateInteraction } from './WellPlate.interaction'\nimport {\n sampleTint,\n wellBadge,\n wellClasses,\n wellLabel,\n wellStyle,\n} from './WellPlate.rendering'\nimport { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\ninterface Props {\n /** Structure built with defineWellPlate(). Optional: every option has a default. */\n spec?: WellPlateSpec\n /** Selected well ids. Bind with `v-model`. */\n modelValue?: string[]\n /** Well data keyed by well id. */\n wells?: Record<string, Partial<Well>>\n disabled?: boolean\n readonly?: boolean\n /** Replace the plate grid with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the plate grid is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /**\n * Opt-in empty-state headline. A blank plate is meaningful, so the empty\n * state only replaces the grid when this message is supplied and no wells\n * carry data.\n */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n spec: () => ({}),\n modelValue: () => [],\n wells: () => ({}),\n disabled: false,\n readonly: false,\n})\n\nconst config = computed(() => applySpecDefaults({\n format: 96,\n selectionMode: 'multiple',\n showLabels: true,\n showWellIds: false,\n heatmap: { enabled: false },\n sampleColors: {},\n zoom: 1,\n size: 'md',\n wellShape: 'rounded',\n showWellLabels: false,\n showBadges: false,\n editable: false,\n editFields: ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n showLegend: false,\n columnConditions: [],\n rowConditions: [],\n allowSampleDrop: false,\n}, props.spec))\n\nconst emit = defineEmits<{\n 'update:modelValue': [wellIds: string[]]\n 'well-click': [wellId: string, event: MouseEvent]\n 'well-hover': [wellId: string | null, event?: MouseEvent]\n 'selection-change': [wellIds: string[]]\n 'context-menu': [wellId: string, event: MouseEvent]\n 'well-move': [sourceWellId: string, targetWellId: string]\n 'well-edit': [wellId: string, data: WellEditData]\n 'well-clear': [wellId: string]\n 'sample-drop': [wellId: string, data: WellSampleDropData, event: DragEvent]\n}>()\n\ninterface WellEditorSlotProps {\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n}\n\ndefineSlots<{\n 'well-editor'?: (props: WellEditorSlotProps) => unknown\n /** Replaces the default loading placeholder. */\n loading?: () => unknown\n /** Replaces the default error placeholder. */\n error?: (props: { error: string }) => unknown\n /** Replaces the default empty placeholder. */\n empty?: () => unknown\n}>()\n\nconst plateConfig = computed(() => PLATE_CONFIGS[config.value.format])\n\nconst rowLabels = computed(() => createPlateRowLabels(plateConfig.value))\n\nconst colLabels = computed(() => createPlateColumnLabels(plateConfig.value))\n\nconst wellGrid = computed(() => createWellGrid(plateConfig.value, rowLabels.value, props.wells))\n\nconst sizeConfig = computed(() => WELL_PLATE_SIZE_CONFIGS[config.value.size])\n\nconst isFillMode = computed(() => config.value.size === 'fill')\n\nconst activeLegendItems = computed(() => config.value.legendItems ?? DEFAULT_WELL_LEGEND_ITEMS)\n\n// --- Condition header helpers ---\n\nconst hasColumnConditions = computed(() => config.value.columnConditions.length > 0)\nconst hasRowConditions = computed(() => config.value.rowConditions.length > 0)\n\nconst colConditionMap = computed(() => createColumnConditionMap(config.value.columnConditions))\nconst rowConditionMap = computed(() => createRowConditionMap(config.value.rowConditions))\nconst colConditionSpans = computed(() => createColumnConditionSpans(colLabels.value, colConditionMap.value))\nconst rowConditionSpans = computed(() => createRowConditionSpans(rowLabels.value, rowConditionMap.value))\nconst rowConditionSpanByRow = computed(() => createRowConditionSpanByRow(rowConditionSpans.value))\n\nconst {\n dragSourceWell,\n dragTargetWell,\n hoveredWell,\n editingWellId,\n editPopupPosition,\n dragSelectedWells,\n isSelected,\n handleWellClick,\n handleEditSave,\n handleEditClear,\n handleEditClose,\n handleWellMouseDown,\n handleWellMouseEnter,\n handleWellMouseLeave,\n handleMouseUp,\n handleContextMenu,\n handleDragStart,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleDragEnd,\n handleWellMoveKeydown,\n handleKeyDown,\n moveAnnouncement,\n moveAnnouncerStyle,\n} = useWellPlateInteraction({\n modelValue: () => props.modelValue,\n selectionMode: () => config.value.selectionMode,\n disabled: () => props.disabled,\n readonly: () => props.readonly,\n editable: () => config.value.editable,\n allowSampleDrop: () => config.value.allowSampleDrop,\n sampleDropParser: () => config.value.sampleDropParser,\n rowLabels: () => rowLabels.value,\n wellGrid: () => wellGrid.value,\n emitSelected: (wellIds) => {\n emit('update:modelValue', wellIds)\n emit('selection-change', wellIds)\n },\n emitWellClick: (wellId, event) => emit('well-click', wellId, event),\n emitWellHover: (wellId, event) => emit('well-hover', wellId, event),\n emitContextMenu: (wellId, event) => emit('context-menu', wellId, event),\n emitWellMove: (sourceWellId, targetWellId) => emit('well-move', sourceWellId, targetWellId),\n emitWellEdit: (wellId, data) => emit('well-edit', wellId, data),\n emitWellClear: wellId => emit('well-clear', wellId),\n emitSampleDrop: (wellId, data, event) => emit('sample-drop', wellId, data, event),\n})\n\nfunction getWellClasses(well: Well): WellClassValue[] {\n const base = wellClasses(well, {\n wellShape: config.value.wellShape,\n selectionMode: config.value.selectionMode,\n selected: isSelected(well.id),\n dragOver: dragSelectedWells.value.has(well.id),\n hovered: hoveredWell.value === well.id,\n disabled: props.disabled || well.state === 'disabled',\n readonly: props.readonly,\n dragSource: dragSourceWell.value === well.id,\n dragTarget: dragTargetWell.value === well.id,\n })\n const custom = config.value.wellClass?.(well)\n return custom ? [base, custom] : [base]\n}\n\nfunction getWellStyle(well: Well): Record<string, string | number | undefined> {\n return {\n ...wellStyle(well, {\n heatmap: config.value.heatmap,\n sampleColors: config.value.sampleColors,\n }),\n ...config.value.wellStyle?.(well),\n }\n}\n\nfunction getWellLabel(well: Well): string | undefined {\n return wellLabel(well, config.value.showWellLabels)\n}\n\n// A group is otherwise shown by color alone; name it from the legend.\nfunction getWellGroupName(well: Well): string | undefined {\n return well.group && (config.value.legendItems?.find(item => item.type === well.group)?.label ?? well.group)\n}\n\n// Names the sample even when the cell is too small to draw it.\nfunction getWellAriaLabel(well: Well): string {\n const name = well.metadata?.label\n const group = getWellGroupName(well)\n return `Well ${well.id}${name ? `, Name: ${name}` : ''}${well.sampleType ? `, Sample type: ${well.sampleType}` : ''}${group ? `, Group: ${group}` : ''}${well.value !== undefined ? `, Value: ${well.value}` : ''}`\n}\n\nfunction getWellBadge(well: Well): { text: string; color: string } | null {\n return wellBadge(well, config.value.showBadges)\n}\n\nuseEventListener(() => document, 'mouseup', handleMouseUp)\n\nconst containerStyle = computed(() =>\n isFillMode.value ? {} : {\n transform: `scale(${config.value.zoom})`,\n transformOrigin: 'top left',\n }\n)\n\n/** Empty state is opt-in: it needs an `emptyText` and a plate with no well data. */\nconst showEmptyState = computed(() =>\n Boolean(props.emptyText) && Object.keys(props.wells).length === 0,\n)\n\nconst tableStyle = computed(() => ({\n borderCollapse: 'separate' as const,\n borderSpacing: sizeConfig.value.gap,\n ...(isFillMode.value ? { width: '100%', tableLayout: 'fixed' as const } : {}),\n}))\n</script>\n\n<template>\n <div\n :class=\"['mint-well-plate', isFillMode ? 'mint-well-plate--fill' : 'mint-well-plate--inline']\"\n :style=\"containerStyle\"\n @keydown=\"handleKeyDown\"\n >\n <div v-if=\"error\" class=\"mint-well-plate__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" :title=\"error\" />\n </slot>\n </div>\n\n <div v-else-if=\"loading\" class=\"mint-well-plate__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" loading-label=\"Loading plate\" />\n </slot>\n </div>\n\n <div v-else-if=\"showEmptyState\" class=\"mint-well-plate__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" :title=\"emptyText\" />\n </slot>\n </div>\n\n <div v-else class=\"mint-well-plate__scroll\">\n <div class=\"mint-well-plate__content\">\n <table\n class=\"mint-well-plate__table\"\n role=\"grid\"\n :aria-label=\"`${config.format}-well plate`\"\n :style=\"tableStyle\"\n >\n <!-- Column condition label row (drug name + unit) -->\n <thead v-if=\"hasColumnConditions\">\n <tr>\n <!-- Spacer for row header column -->\n <th :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <!-- Spacer for row condition column -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <template v-for=\"(span, idx) in colConditionSpans\" :key=\"'clabel-' + idx\">\n <th\n v-if=\"'condition' in span\"\n :colspan=\"span.colspan\"\n class=\"mint-well-plate__condition-label\"\n :style=\"{\n height: sizeConfig.headerHeight,\n fontSize: sizeConfig.fontSize,\n color: span.condition.color,\n }\"\n >\n {{ span.condition.label }}<template v-if=\"span.condition.unit\"> ({{ span.condition.unit }})</template>\n </th>\n <th v-else :colspan=\"span.colspan\"></th>\n </template>\n </tr>\n </thead>\n <!-- Column headers (with concentration overlay when conditions present) -->\n <thead v-if=\"config.showLabels\">\n <tr>\n <th :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <!-- Spacer for row condition column in column header row -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <th\n v-for=\"col in colLabels\"\n :key=\"col\"\n class=\"mint-well-plate__col-header\"\n :style=\"{ height: sizeConfig.headerHeight, fontSize: sizeConfig.fontSize }\"\n >\n <template v-for=\"entry in [colConditionMap.get(col)]\" :key=\"col\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ col }}</template>\n </template>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, rowIndex) in wellGrid\" :key=\"rowIndex\" role=\"row\">\n <!-- Row condition label cell (drug name, spans multiple rows) -->\n <template v-if=\"hasRowConditions && rowConditionSpanByRow.has(rowIndex)\">\n <td\n v-for=\"span in [rowConditionSpanByRow.get(rowIndex)!]\"\n :key=\"rowIndex\"\n :rowspan=\"span.rowspan\"\n :class=\"[\n 'mint-well-plate__row-condition-label',\n 'gap' in span ? 'mint-well-plate__row-condition-label--empty' : '',\n ]\"\n :style=\"{\n width: sizeConfig.headerWidth,\n minWidth: sizeConfig.headerWidth,\n fontSize: sizeConfig.fontSize,\n ...('condition' in span ? { color: span.condition.color } : {}),\n }\"\n >\n <div v-if=\"'condition' in span\" class=\"mint-well-plate__row-condition-wrap\">\n <span class=\"mint-well-plate__row-condition-text\">{{ span.condition.label }}</span>\n </div>\n </td>\n </template>\n <!-- Row header (with concentration overlay when conditions present) -->\n <td\n v-if=\"config.showLabels\"\n class=\"mint-well-plate__row-header\"\n :style=\"{\n width: sizeConfig.headerWidth,\n height: sizeConfig.cellHeight,\n minWidth: sizeConfig.headerWidth,\n minHeight: sizeConfig.cellHeight,\n fontSize: sizeConfig.fontSize,\n }\"\n >\n <template v-for=\"entry in [rowConditionMap.get(rowLabels[rowIndex])]\" :key=\"rowIndex\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ rowLabels[rowIndex] }}</template>\n </template>\n </td>\n\n <!-- Wells -->\n <td v-for=\"well in row\" :key=\"well.id\" class=\"mint-well-plate__cell\">\n <div\n role=\"gridcell\"\n tabindex=\"0\"\n :aria-label=\"getWellAriaLabel(well)\"\n :aria-selected=\"isSelected(well.id)\"\n :aria-disabled=\"props.disabled || well.state === 'disabled'\"\n :draggable=\"config.selectionMode === 'drag' && !!well.sampleType\"\n :class=\"getWellClasses(well)\"\n :style=\"{\n width: isFillMode ? '100%' : sizeConfig.cellWidth,\n height: sizeConfig.cellHeight,\n minWidth: isFillMode ? '0' : sizeConfig.cellWidth,\n minHeight: sizeConfig.cellHeight,\n boxSizing: 'border-box',\n fontSize: sizeConfig.fontSize,\n ...getWellStyle(well),\n }\"\n :title=\"`${well.id}${well.sampleType ? `: ${well.sampleType}` : ''}${getWellGroupName(well) ? `, ${getWellGroupName(well)}` : ''}${getWellLabel(well) ? ` - ${getWellLabel(well)}` : ''}`\"\n @click=\"handleWellClick(well, $event)\"\n @mousedown=\"handleWellMouseDown(well, $event)\"\n @mouseenter=\"handleWellMouseEnter(well, $event)\"\n @mouseleave=\"handleWellMouseLeave\"\n @contextmenu=\"handleContextMenu(well, $event)\"\n @dragstart=\"handleDragStart(well, $event)\"\n @dragover=\"handleDragOver(well, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(well, $event)\"\n @dragend=\"handleDragEnd\"\n @keydown=\"handleWellMoveKeydown(well, $event)\"\n @keydown.enter.exact=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.space.exact.prevent=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.enter.shift.exact=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.space.shift.exact.prevent=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n >\n <!-- Well label text (from metadata.label) -->\n <span\n v-if=\"getWellLabel(well)\"\n class=\"mint-well-plate__label\"\n >\n {{ getWellLabel(well) }}\n </span>\n <!-- Well ID -->\n <span\n v-else-if=\"config.showWellIds\"\n class=\"mint-well-plate__well-id\"\n >\n {{ well.id }}\n </span>\n <!-- QC / iQC marker glyph when the centre is free -->\n <span\n v-else-if=\"well.sampleType === 'qc' || well.sampleType === 'iqc'\"\n class=\"mint-well-plate__indicator\"\n >\n {{ well.sampleType === 'qc' ? 'Q' : 'i' }}\n </span>\n\n <!-- Badge (injection count or custom method) -->\n <span\n v-if=\"getWellBadge(well)\"\n class=\"mint-well-plate__badge\"\n :style=\"{ backgroundColor: getWellBadge(well)?.color }\"\n :title=\"getWellBadge(well)?.text === '+' ? 'Custom method' : `${getWellBadge(well)?.text ?? ''}x injections`\"\n >\n {{ getWellBadge(well)?.text }}\n </span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Heatmap legend (inside content wrapper to match table width) -->\n <div\n v-if=\"config.heatmap?.enabled && config.heatmap.showLegend\"\n class=\"mint-well-plate__legend\"\n >\n <span class=\"mint-well-plate__legend-label\">{{ config.heatmap.min ?? 0 }}</span>\n <div class=\"mint-well-plate__legend-bar\">\n <div\n v-for=\"(color, index) in (config.heatmap.colorScale === 'custom' && config.heatmap.customColors?.length ? config.heatmap.customColors : HEATMAP_COLORS[config.heatmap.colorScale || 'viridis'])\"\n :key=\"index\"\n class=\"mint-well-plate__legend-segment\"\n :style=\"{ backgroundColor: color }\"\n />\n </div>\n <span class=\"mint-well-plate__legend-label\">{{ config.heatmap.max ?? 1 }}</span>\n </div>\n\n <!-- Sample type legend bar -->\n <div v-if=\"config.showLegend\" class=\"mint-well-plate__sample-legend\">\n <div\n v-for=\"item in activeLegendItems\"\n :key=\"item.type\"\n class=\"mint-well-plate__sample-legend-item\"\n >\n <span\n class=\"mint-well-plate__sample-legend-dot\"\n :style=\"sampleTint(item.color)\"\n />\n <span class=\"mint-well-plate__sample-legend-text\">{{ item.label }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Edit popup -->\n <template v-if=\"config.editable && editingWellId\">\n <slot\n name=\"well-editor\"\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"config.editFields\"\n :default-injection-volume=\"config.defaultInjectionVolume\"\n :position=\"editPopupPosition\"\n :save=\"handleEditSave\"\n :clear=\"handleEditClear\"\n :close=\"handleEditClose\"\n >\n <WellEditPopupInternal\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"config.editFields\"\n :default-injection-volume=\"config.defaultInjectionVolume\"\n :marker-colors=\"config.sampleColors\"\n :position=\"editPopupPosition\"\n @save=\"handleEditSave\"\n @clear=\"handleEditClear\"\n @close=\"handleEditClose\"\n />\n </slot>\n </template>\n\n <!-- Keyboard well-move announcements for screen readers -->\n <div role=\"status\" aria-live=\"polite\" :style=\"moveAnnouncerStyle\">{{ moveAnnouncement }}</div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Interactive 96/384-well plate grid with drag-to-select, heatmap overlays, per-well editing, and sample-color mapping. */\nimport { computed } from 'vue'\nimport type { Well, WellEditField, WellEditData, WellSampleDropData, WellClassValue, WellPlateSpec } from '../types'\nimport { applySpecDefaults } from '../composables/componentSpecs'\nimport { useEventListener } from '../composables/useEventListener'\nimport WellEditPopupInternal from './internal/WellEditPopupInternal.vue'\nimport {\n conditionGradientStyle,\n formatConcentration as formatConc,\n HEATMAP_COLORS,\n} from './WellPlate.colors'\nimport {\n createColumnConditionMap,\n createColumnConditionSpans,\n createRowConditionMap,\n createRowConditionSpanByRow,\n createRowConditionSpans,\n} from './WellPlate.conditions'\nimport {\n createPlateColumnLabels,\n createPlateRowLabels,\n createWellGrid,\n PLATE_CONFIGS,\n WELL_PLATE_SIZE_CONFIGS,\n} from './WellPlate.geometry'\nimport { useWellPlateInteraction } from './WellPlate.interaction'\nimport {\n sampleTint,\n wellBadge,\n wellClasses,\n wellLabel,\n wellStyle,\n} from './WellPlate.rendering'\nimport { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\ninterface Props {\n /** Structure built with defineWellPlate(). Optional: every option has a default. */\n spec?: WellPlateSpec\n /** Selected well ids. Bind with `v-model`. */\n modelValue?: string[]\n /** Well data keyed by well id. */\n wells?: Record<string, Partial<Well>>\n disabled?: boolean\n readonly?: boolean\n /** Replace the plate grid with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the plate grid is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /**\n * Opt-in empty-state headline. A blank plate is meaningful, so the empty\n * state only replaces the grid when this message is supplied and no wells\n * carry data.\n */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n spec: () => ({}),\n modelValue: () => [],\n wells: () => ({}),\n disabled: false,\n readonly: false,\n})\n\nconst config = computed(() => applySpecDefaults({\n format: 96,\n selectionMode: 'multiple',\n showLabels: true,\n showWellIds: false,\n heatmap: { enabled: false },\n sampleColors: {},\n zoom: 1,\n size: 'md',\n wellShape: 'rounded',\n showWellLabels: false,\n showBadges: false,\n editable: false,\n editFields: ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n defaultInjectionVolume: 5,\n showLegend: false,\n columnConditions: [],\n rowConditions: [],\n allowSampleDrop: false,\n}, props.spec))\n\nconst emit = defineEmits<{\n 'update:modelValue': [wellIds: string[]]\n 'well-click': [wellId: string, event: MouseEvent]\n 'well-hover': [wellId: string | null, event?: MouseEvent]\n 'selection-change': [wellIds: string[]]\n 'context-menu': [wellId: string, event: MouseEvent]\n 'well-move': [sourceWellId: string, targetWellId: string]\n 'well-edit': [wellId: string, data: WellEditData]\n 'well-clear': [wellId: string]\n 'sample-drop': [wellId: string, data: WellSampleDropData, event: DragEvent]\n}>()\n\ninterface WellEditorSlotProps {\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n}\n\ndefineSlots<{\n 'well-editor'?: (props: WellEditorSlotProps) => unknown\n /** Replaces the default loading placeholder. */\n loading?: () => unknown\n /** Replaces the default error placeholder. */\n error?: (props: { error: string }) => unknown\n /** Replaces the default empty placeholder. */\n empty?: () => unknown\n}>()\n\nconst plateConfig = computed(() => PLATE_CONFIGS[config.value.format])\n\nconst rowLabels = computed(() => createPlateRowLabels(plateConfig.value))\n\nconst colLabels = computed(() => createPlateColumnLabels(plateConfig.value))\n\nconst wellGrid = computed(() => createWellGrid(plateConfig.value, rowLabels.value, props.wells))\n\nconst sizeConfig = computed(() => WELL_PLATE_SIZE_CONFIGS[config.value.size])\n\nconst isFillMode = computed(() => config.value.size === 'fill')\n\nconst activeLegendItems = computed(() => config.value.legendItems ?? DEFAULT_WELL_LEGEND_ITEMS)\n\n// --- Condition header helpers ---\n\nconst hasColumnConditions = computed(() => config.value.columnConditions.length > 0)\nconst hasRowConditions = computed(() => config.value.rowConditions.length > 0)\n\nconst colConditionMap = computed(() => createColumnConditionMap(config.value.columnConditions))\nconst rowConditionMap = computed(() => createRowConditionMap(config.value.rowConditions))\nconst colConditionSpans = computed(() => createColumnConditionSpans(colLabels.value, colConditionMap.value))\nconst rowConditionSpans = computed(() => createRowConditionSpans(rowLabels.value, rowConditionMap.value))\nconst rowConditionSpanByRow = computed(() => createRowConditionSpanByRow(rowConditionSpans.value))\n\nconst {\n dragSourceWell,\n dragTargetWell,\n hoveredWell,\n editingWellId,\n editPopupPosition,\n dragSelectedWells,\n isSelected,\n handleWellClick,\n handleEditSave,\n handleEditClear,\n handleEditClose,\n handleWellMouseDown,\n handleWellMouseEnter,\n handleWellMouseLeave,\n handleMouseUp,\n handleContextMenu,\n handleDragStart,\n handleDragOver,\n handleDragLeave,\n handleDrop,\n handleDragEnd,\n handleWellMoveKeydown,\n handleKeyDown,\n moveAnnouncement,\n moveAnnouncerStyle,\n} = useWellPlateInteraction({\n modelValue: () => props.modelValue,\n selectionMode: () => config.value.selectionMode,\n disabled: () => props.disabled,\n readonly: () => props.readonly,\n editable: () => config.value.editable,\n allowSampleDrop: () => config.value.allowSampleDrop,\n sampleDropParser: () => config.value.sampleDropParser,\n rowLabels: () => rowLabels.value,\n wellGrid: () => wellGrid.value,\n emitSelected: (wellIds) => {\n emit('update:modelValue', wellIds)\n emit('selection-change', wellIds)\n },\n emitWellClick: (wellId, event) => emit('well-click', wellId, event),\n emitWellHover: (wellId, event) => emit('well-hover', wellId, event),\n emitContextMenu: (wellId, event) => emit('context-menu', wellId, event),\n emitWellMove: (sourceWellId, targetWellId) => emit('well-move', sourceWellId, targetWellId),\n emitWellEdit: (wellId, data) => emit('well-edit', wellId, data),\n emitWellClear: wellId => emit('well-clear', wellId),\n emitSampleDrop: (wellId, data, event) => emit('sample-drop', wellId, data, event),\n})\n\nfunction getWellClasses(well: Well): WellClassValue[] {\n const base = wellClasses(well, {\n wellShape: config.value.wellShape,\n selectionMode: config.value.selectionMode,\n selected: isSelected(well.id),\n dragOver: dragSelectedWells.value.has(well.id),\n hovered: hoveredWell.value === well.id,\n disabled: props.disabled || well.state === 'disabled',\n readonly: props.readonly,\n dragSource: dragSourceWell.value === well.id,\n dragTarget: dragTargetWell.value === well.id,\n })\n const custom = config.value.wellClass?.(well)\n return custom ? [base, custom] : [base]\n}\n\nfunction getWellStyle(well: Well): Record<string, string | number | undefined> {\n return {\n ...wellStyle(well, {\n heatmap: config.value.heatmap,\n sampleColors: config.value.sampleColors,\n }),\n ...config.value.wellStyle?.(well),\n }\n}\n\nfunction getWellLabel(well: Well): string | undefined {\n return wellLabel(well, config.value.showWellLabels)\n}\n\n// A group is otherwise shown by color alone; name it from the legend.\nfunction getWellGroupName(well: Well): string | undefined {\n return well.group && (config.value.legendItems?.find(item => item.type === well.group)?.label ?? well.group)\n}\n\n// Names the sample even when the cell is too small to draw it.\nfunction getWellAriaLabel(well: Well): string {\n const name = well.metadata?.label\n const group = getWellGroupName(well)\n return `Well ${well.id}${name ? `, Name: ${name}` : ''}${well.sampleType ? `, Sample type: ${well.sampleType}` : ''}${group ? `, Group: ${group}` : ''}${well.value !== undefined ? `, Value: ${well.value}` : ''}`\n}\n\nfunction getWellBadge(well: Well): { text: string; color: string } | null {\n return wellBadge(well, config.value.showBadges)\n}\n\nuseEventListener(() => document, 'mouseup', handleMouseUp)\n\nconst containerStyle = computed(() =>\n isFillMode.value ? {} : {\n transform: `scale(${config.value.zoom})`,\n transformOrigin: 'top left',\n }\n)\n\n/** Empty state is opt-in: it needs an `emptyText` and a plate with no well data. */\nconst showEmptyState = computed(() =>\n Boolean(props.emptyText) && Object.keys(props.wells).length === 0,\n)\n\nconst tableStyle = computed(() => ({\n borderCollapse: 'separate' as const,\n borderSpacing: sizeConfig.value.gap,\n ...(isFillMode.value ? { width: '100%', tableLayout: 'fixed' as const } : {}),\n}))\n</script>\n\n<template>\n <div\n :class=\"['mint-well-plate', isFillMode ? 'mint-well-plate--fill' : 'mint-well-plate--inline']\"\n :style=\"containerStyle\"\n @keydown=\"handleKeyDown\"\n >\n <div v-if=\"error\" class=\"mint-well-plate__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" :title=\"error\" />\n </slot>\n </div>\n\n <div v-else-if=\"loading\" class=\"mint-well-plate__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" loading-label=\"Loading plate\" />\n </slot>\n </div>\n\n <div v-else-if=\"showEmptyState\" class=\"mint-well-plate__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" :title=\"emptyText\" />\n </slot>\n </div>\n\n <div v-else class=\"mint-well-plate__scroll\">\n <div class=\"mint-well-plate__content\">\n <table\n class=\"mint-well-plate__table\"\n role=\"grid\"\n :aria-label=\"`${config.format}-well plate`\"\n :style=\"tableStyle\"\n >\n <!-- Column condition label row (drug name + unit) -->\n <thead v-if=\"hasColumnConditions\">\n <tr>\n <!-- Spacer for row header column -->\n <th :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <!-- Spacer for row condition column -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth }\"></th>\n <template v-for=\"(span, idx) in colConditionSpans\" :key=\"'clabel-' + idx\">\n <th\n v-if=\"'condition' in span\"\n :colspan=\"span.colspan\"\n class=\"mint-well-plate__condition-label\"\n :style=\"{\n height: sizeConfig.headerHeight,\n fontSize: sizeConfig.fontSize,\n color: span.condition.color,\n }\"\n >\n {{ span.condition.label }}<template v-if=\"span.condition.unit\"> ({{ span.condition.unit }})</template>\n </th>\n <th v-else :colspan=\"span.colspan\"></th>\n </template>\n </tr>\n </thead>\n <!-- Column headers (with concentration overlay when conditions present) -->\n <thead v-if=\"config.showLabels\">\n <tr>\n <th :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <!-- Spacer for row condition column in column header row -->\n <th v-if=\"hasRowConditions\" :style=\"{ width: sizeConfig.headerWidth, height: sizeConfig.headerHeight }\"></th>\n <th\n v-for=\"col in colLabels\"\n :key=\"col\"\n class=\"mint-well-plate__col-header\"\n :style=\"{ height: sizeConfig.headerHeight, fontSize: sizeConfig.fontSize }\"\n >\n <template v-for=\"entry in [colConditionMap.get(col)]\" :key=\"col\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ col }}</template>\n </template>\n </th>\n </tr>\n </thead>\n <tbody>\n <tr v-for=\"(row, rowIndex) in wellGrid\" :key=\"rowIndex\" role=\"row\">\n <!-- Row condition label cell (drug name, spans multiple rows) -->\n <template v-if=\"hasRowConditions && rowConditionSpanByRow.has(rowIndex)\">\n <td\n v-for=\"span in [rowConditionSpanByRow.get(rowIndex)!]\"\n :key=\"rowIndex\"\n :rowspan=\"span.rowspan\"\n :class=\"[\n 'mint-well-plate__row-condition-label',\n 'gap' in span ? 'mint-well-plate__row-condition-label--empty' : '',\n ]\"\n :style=\"{\n width: sizeConfig.headerWidth,\n minWidth: sizeConfig.headerWidth,\n fontSize: sizeConfig.fontSize,\n ...('condition' in span ? { color: span.condition.color } : {}),\n }\"\n >\n <div v-if=\"'condition' in span\" class=\"mint-well-plate__row-condition-wrap\">\n <span class=\"mint-well-plate__row-condition-text\">{{ span.condition.label }}</span>\n </div>\n </td>\n </template>\n <!-- Row header (with concentration overlay when conditions present) -->\n <td\n v-if=\"config.showLabels\"\n class=\"mint-well-plate__row-header\"\n :style=\"{\n width: sizeConfig.headerWidth,\n height: sizeConfig.cellHeight,\n minWidth: sizeConfig.headerWidth,\n minHeight: sizeConfig.cellHeight,\n fontSize: sizeConfig.fontSize,\n }\"\n >\n <template v-for=\"entry in [rowConditionMap.get(rowLabels[rowIndex])]\" :key=\"rowIndex\">\n <span\n v-if=\"entry\"\n class=\"mint-well-plate__condition-cell\"\n :style=\"conditionGradientStyle(\n entry.condition.color,\n entry.condition.concentrations[entry.indexInGroup],\n entry.condition.concentrations,\n )\"\n >\n {{ formatConc(entry.condition.concentrations[entry.indexInGroup]) }}\n </span>\n <template v-else>{{ rowLabels[rowIndex] }}</template>\n </template>\n </td>\n\n <!-- Wells -->\n <td v-for=\"well in row\" :key=\"well.id\" class=\"mint-well-plate__cell\">\n <div\n role=\"gridcell\"\n tabindex=\"0\"\n :aria-label=\"getWellAriaLabel(well)\"\n :aria-selected=\"isSelected(well.id)\"\n :aria-disabled=\"props.disabled || well.state === 'disabled'\"\n :draggable=\"config.selectionMode === 'drag' && !!well.sampleType\"\n :class=\"getWellClasses(well)\"\n :style=\"{\n width: isFillMode ? '100%' : sizeConfig.cellWidth,\n height: sizeConfig.cellHeight,\n minWidth: isFillMode ? '0' : sizeConfig.cellWidth,\n minHeight: sizeConfig.cellHeight,\n boxSizing: 'border-box',\n fontSize: sizeConfig.fontSize,\n ...getWellStyle(well),\n }\"\n :title=\"`${well.id}${well.sampleType ? `: ${well.sampleType}` : ''}${getWellGroupName(well) ? `, ${getWellGroupName(well)}` : ''}${getWellLabel(well) ? ` - ${getWellLabel(well)}` : ''}`\"\n @click=\"handleWellClick(well, $event)\"\n @mousedown=\"handleWellMouseDown(well, $event)\"\n @mouseenter=\"handleWellMouseEnter(well, $event)\"\n @mouseleave=\"handleWellMouseLeave\"\n @contextmenu=\"handleContextMenu(well, $event)\"\n @dragstart=\"handleDragStart(well, $event)\"\n @dragover=\"handleDragOver(well, $event)\"\n @dragleave=\"handleDragLeave\"\n @drop=\"handleDrop(well, $event)\"\n @dragend=\"handleDragEnd\"\n @keydown=\"handleWellMoveKeydown(well, $event)\"\n @keydown.enter.exact=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.space.exact.prevent=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.enter.shift.exact=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n @keydown.space.shift.exact.prevent=\"handleWellClick(well, $event as unknown as MouseEvent)\"\n >\n <!-- Well label text (from metadata.label) -->\n <span\n v-if=\"getWellLabel(well)\"\n class=\"mint-well-plate__label\"\n >\n {{ getWellLabel(well) }}\n </span>\n <!-- Well ID -->\n <span\n v-else-if=\"config.showWellIds\"\n class=\"mint-well-plate__well-id\"\n >\n {{ well.id }}\n </span>\n <!-- QC / iQC marker glyph when the centre is free -->\n <span\n v-else-if=\"well.sampleType === 'qc' || well.sampleType === 'iqc'\"\n class=\"mint-well-plate__indicator\"\n >\n {{ well.sampleType === 'qc' ? 'Q' : 'i' }}\n </span>\n\n <!-- Badge (injection count or custom method) -->\n <span\n v-if=\"getWellBadge(well)\"\n class=\"mint-well-plate__badge\"\n :style=\"{ backgroundColor: getWellBadge(well)?.color }\"\n :title=\"getWellBadge(well)?.text === '+' ? 'Custom method' : `${getWellBadge(well)?.text ?? ''}x injections`\"\n >\n {{ getWellBadge(well)?.text }}\n </span>\n </div>\n </td>\n </tr>\n </tbody>\n </table>\n\n <!-- Heatmap legend (inside content wrapper to match table width) -->\n <div\n v-if=\"config.heatmap?.enabled && config.heatmap.showLegend\"\n class=\"mint-well-plate__legend\"\n >\n <span class=\"mint-well-plate__legend-label\">{{ config.heatmap.min ?? 0 }}</span>\n <div class=\"mint-well-plate__legend-bar\">\n <div\n v-for=\"(color, index) in (config.heatmap.colorScale === 'custom' && config.heatmap.customColors?.length ? config.heatmap.customColors : HEATMAP_COLORS[config.heatmap.colorScale || 'viridis'])\"\n :key=\"index\"\n class=\"mint-well-plate__legend-segment\"\n :style=\"{ backgroundColor: color }\"\n />\n </div>\n <span class=\"mint-well-plate__legend-label\">{{ config.heatmap.max ?? 1 }}</span>\n </div>\n\n <!-- Sample type legend bar -->\n <div v-if=\"config.showLegend\" class=\"mint-well-plate__sample-legend\">\n <div\n v-for=\"item in activeLegendItems\"\n :key=\"item.type\"\n class=\"mint-well-plate__sample-legend-item\"\n >\n <span\n class=\"mint-well-plate__sample-legend-dot\"\n :style=\"sampleTint(item.color)\"\n />\n <span class=\"mint-well-plate__sample-legend-text\">{{ item.label }}</span>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Edit popup -->\n <template v-if=\"config.editable && editingWellId\">\n <slot\n name=\"well-editor\"\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"config.editFields\"\n :default-injection-volume=\"config.defaultInjectionVolume\"\n :position=\"editPopupPosition\"\n :save=\"handleEditSave\"\n :clear=\"handleEditClear\"\n :close=\"handleEditClose\"\n >\n <WellEditPopupInternal\n :well-id=\"editingWellId\"\n :well-data=\"wells[editingWellId]\"\n :edit-fields=\"config.editFields\"\n :default-injection-volume=\"config.defaultInjectionVolume\"\n :marker-colors=\"config.sampleColors\"\n :position=\"editPopupPosition\"\n @save=\"handleEditSave\"\n @clear=\"handleEditClear\"\n @close=\"handleEditClose\"\n />\n </slot>\n </template>\n\n <!-- Keyboard well-move announcements for screen readers -->\n <div role=\"status\" aria-live=\"polite\" :style=\"moveAnnouncerStyle\">{{ moveAnnouncement }}</div>\n </div>\n</template>\n","/**\n * Shared color scales and color math for the MINT SDK.\n *\n * ## Why the exported values are hex, not `var(--…)`\n *\n * Every scale below is mirrored by a CSS custom property in\n * `styles/variables.css` (`--mint-sample-1…9`), so\n * stylesheets and `:style` bindings can stay theme-aware by referencing the\n * token. The JavaScript constants deliberately carry the plain hex **fallback**\n * for the same slot, because these values are also\n *\n * - persisted into experiment design data (`SampleType.color`), where a\n * `var(--…)` string would be meaningless outside the browser, and\n * - handed to canvas / SVG / export code paths, which cannot resolve CSS\n * custom properties.\n *\n * Keep the two in sync: a change here must be mirrored in `variables.css`.\n */\n\nimport type { SlotPosition } from '../types/componentLabTypes'\n\n/**\n * Canonical categorical scale for sample types, plate maps, dose-response\n * series and legends. Mirrors `--mint-sample-1` … `--mint-sample-9`.\n */\nexport const SAMPLE_COLOR_SCALE: readonly string[] = [\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\n/**\n * Group palette as CSS tokens (3a): groups always take `--mint-sample-1` …\n * `--mint-sample-8`; the 9th (gray) stays out of the group cycle.\n */\nexport const SAMPLE_GROUP_TOKENS: readonly string[] = [\n 'var(--mint-sample-1)',\n 'var(--mint-sample-2)',\n 'var(--mint-sample-3)',\n 'var(--mint-sample-4)',\n 'var(--mint-sample-5)',\n 'var(--mint-sample-6)',\n 'var(--mint-sample-7)',\n 'var(--mint-sample-8)',\n]\n\n/** Slot positions in their canonical display order. */\nexport const SLOT_ORDER: readonly SlotPosition[] = ['R', 'G', 'B', 'Y']\n\n/**\n * Color utilities for deriving same-hue-family shades.\n *\n * Used by SampleSelector and other group/legend UIs where a parent color\n * needs to seed a gradient of related shades for its children.\n */\n\nexport interface Hsl {\n h: number // 0–360\n s: number // 0–100\n l: number // 0–100\n}\n\nconst HEX_RE = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i\n\nfunction clamp(n: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, n))\n}\n\nexport function hexToHsl(hex: string): Hsl {\n const match = HEX_RE.exec(hex.trim())\n if (!match) return { h: 0, s: 0, l: 50 }\n\n let raw = match[1]\n if (raw.length === 3) raw = raw.split('').map(c => c + c).join('')\n\n const r = parseInt(raw.slice(0, 2), 16) / 255\n const g = parseInt(raw.slice(2, 4), 16) / 255\n const b = parseInt(raw.slice(4, 6), 16) / 255\n\n const max = Math.max(r, g, b)\n const min = Math.min(r, g, b)\n const delta = max - min\n\n let h = 0\n if (delta !== 0) {\n if (max === r) h = ((g - b) / delta) % 6\n else if (max === g) h = (b - r) / delta + 2\n else h = (r - g) / delta + 4\n }\n h = (h * 60 + 360) % 360\n\n const l = (max + min) / 2\n const s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1))\n\n return { h, s: s * 100, l: l * 100 }\n}\n\nexport function hslToHex(h: number, s: number, l: number): string {\n const sNorm = clamp(s, 0, 100) / 100\n const lNorm = clamp(l, 0, 100) / 100\n const hNorm = ((h % 360) + 360) % 360\n\n const c = (1 - Math.abs(2 * lNorm - 1)) * sNorm\n const x = c * (1 - Math.abs(((hNorm / 60) % 2) - 1))\n const m = lNorm - c / 2\n\n let r = 0, g = 0, b = 0\n if (hNorm < 60) [r, g, b] = [c, x, 0]\n else if (hNorm < 120) [r, g, b] = [x, c, 0]\n else if (hNorm < 180) [r, g, b] = [0, c, x]\n else if (hNorm < 240) [r, g, b] = [0, x, c]\n else if (hNorm < 300) [r, g, b] = [x, 0, c]\n else [r, g, b] = [c, 0, x]\n\n const toHex = (n: number) => Math.round((n + m) * 255).toString(16).padStart(2, '0')\n return `#${toHex(r)}${toHex(g)}${toHex(b)}`\n}\n\n/**\n * Derive a same-hue-family shade of `parentHex` for child at `index` of `total`.\n *\n * Lightness is distributed evenly within [35, 75] so all children remain\n * readable on light/dark backgrounds. Hue and saturation are inherited from\n * the parent. With `total <= 1`, returns the parent unchanged.\n *\n * Children are ordered light → dark by index, so index 0 is the brightest\n * sibling and index `total - 1` is the darkest.\n */\nexport function deriveShade(parentHex: string, index: number, total: number): string {\n if (total <= 1) return parentHex\n const { h, s, l } = hexToHsl(parentHex)\n\n const L_MIN = 35\n const L_MAX = 75\n const spread = Math.min(L_MAX - L_MIN, 40)\n const center = clamp(l, L_MIN + spread / 2, L_MAX - spread / 2)\n\n const top = center + spread / 2\n const bot = center - spread / 2\n const t = total === 1 ? 0.5 : index / (total - 1)\n const childL = top - t * (top - bot)\n\n const childS = clamp(s, 35, 100)\n return hslToHex(h, childS, childL)\n}\n","<script setup lang=\"ts\">\n/**\n * Multi-plate editor over WellPlate: plate tabs, format and slot, a single-well\n * inspector, group assignment, well drag and sample drop.\n *\n * At 720px and wider a right rail lists the groups (or sample types) and holds\n * the inspector for a single selected well; narrower, the rail collapses and\n * the inspector opens as a popover.\n *\n * Controlled: it never changes `modelValue`. Every action is emitted as an\n * intent (`well-edit`, `assign`, `rack-add`, …) for the caller to apply — or\n * bind `useRackEditor().plateEditorListeners` to let the SDK hold the state.\n *\n * @example\n * ```vue\n * <PlateEditor v-model:active-rack=\"activeId\" :spec=\"definePlateEditor({ slots: true, fillSeries: true })\" :model-value=\"racks\" @well-edit=\"save\" />\n * ```\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch, type ComponentPublicInstance } from 'vue'\nimport { Download, Move, Plus, Redo2, Trash2, Undo2, Upload, X } from 'lucide-vue-next'\nimport type {\n ActionMenuItem,\n PlateEditorSpec,\n Rack,\n SampleType,\n SlotPosition,\n Well,\n WellEditData,\n WellEditField,\n WellLegendItem,\n WellPlateFormat,\n WellPlateSelectionMode,\n WellSampleDropData,\n WellSampleDropParser,\n} from '../types'\nimport { SAMPLE_GROUP_TOKENS, SLOT_ORDER } from '../utils/color'\nimport { reorderList, useListReorder } from '../composables/useListReorder'\nimport { useRovingFocus } from '../composables/useRovingFocus'\nimport { PLATE_CONFIGS, WELL_PLATE_SIZE_CONFIGS } from './WellPlate.geometry'\nimport { parseDroppedSampleJson } from './WellPlate.sampleDrop'\nimport { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'\nimport { applySpecDefaults, defineWellPlate } from '../composables/componentSpecs'\nimport { sampleTypeMarker } from '../utils/sampleTypeMarker'\nimport WellPlate from './WellPlate.vue'\nimport WellEditPopupInternal from './internal/WellEditPopupInternal.vue'\nimport ActionMenu from './ActionMenu.vue'\nimport BaseButton from './BaseButton.vue'\nimport SegmentedControl from './SegmentedControl.vue'\nimport ConfirmDialog from './ConfirmDialog.vue'\nimport BaseInput from './BaseInput.vue'\n\ninterface Props {\n /** Structure built with definePlateEditor(). Optional: every option has a default. */\n spec?: PlateEditorSpec\n /** The plates. Never mutated; changes arrive as emitted intents. */\n modelValue: Rack[]\n /** Id of the active plate. Bind with `v-model:active-rack`; unbound, the tabs keep their own choice. */\n activeRack?: string\n canUndo?: boolean\n canRedo?: boolean\n /** No editing controls; well clicks still emit `well-click`. */\n readonly?: boolean\n /** Footer text while nothing is selected; replaces the built-in selection hint. */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n spec: () => ({}),\n activeRack: undefined,\n canUndo: false,\n canRedo: false,\n readonly: false,\n})\n\nconst config = computed(() => applySpecDefaults({\n formats: [54, 96],\n slots: false,\n reorder: true,\n wellDrag: false,\n groups: false,\n wellFields: ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n sampleDrop: false,\n import: false,\n importMode: 'file',\n export: false,\n clear: 'confirm',\n history: false,\n fillSeries: false,\n fillSeriesWhenFull: 'disable',\n maxRacks: 10,\n minRacks: 1,\n size: 'xs',\n}, props.spec))\n\nconst emit = defineEmits<{\n 'update:activeRack': [rackId: string]\n 'rack-add': []\n 'rack-remove': [rackId: string]\n 'rack-reorder': [rackIds: string[]]\n 'update:format': [rackId: string, format: WellPlateFormat]\n 'update:slot': [rackId: string, slot: SlotPosition]\n 'well-click': [rackId: string, wellId: string]\n 'well-edit': [rackId: string, wellId: string, data: WellEditData]\n 'well-clear': [rackId: string, wellId: string]\n 'well-move': [rackId: string, sourceWellId: string, targetWellId: string]\n 'sample-drop': [rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent]\n /** `groupId` undefined clears the wells. */\n 'assign': [rackId: string, wellIds: string[], groupId: string | undefined]\n 'clear': [rackId: string]\n 'fill-series': [rackId: string]\n 'import': [file: File]\n /** The pasted text, unparsed. */\n 'import-text': [text: string]\n 'export': [format: string]\n 'group-add': []\n 'undo': []\n 'redo': []\n 'selection-change': [wellIds: string[]]\n}>()\n\ninterface PlateEditorWellEditorSlotProps {\n rack: Rack\n rackId: string\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n /** True in the rail, false in the popover. */\n inline: boolean\n}\n\ndefineSlots<{\n /** Replaces the default single-well inspector, in the rail or the popover. */\n 'well-editor'?: (props: PlateEditorWellEditorSlotProps) => unknown\n /** Extra controls at the right end of the tab bar. */\n 'tab-extra'?: () => unknown\n /** Extra controls at the right end of the toolbar. */\n 'toolbar-extra'?: () => unknown\n}>()\n\n// --- Active plate: a bound activeRack wins; unbound, the tabs keep their own choice ---\nconst localActiveId = ref<string>()\n\nconst activeRack = computed<Rack | undefined>(() => {\n const id = props.activeRack ?? localActiveId.value\n return props.modelValue.find(rack => rack.id === id) ?? props.modelValue[0]\n})\n\nfunction selectRack(rackId: string) {\n localActiveId.value = rackId\n emit('update:activeRack', rackId)\n}\n\nfunction filledCount(rack: Rack): number {\n return Object.values(rack.wells).filter(well => well.sampleType).length\n}\n\n// --- Tabs: roving focus, Alt+←/→ and drag reorder ---\nconst tabRefs = new Map<string, HTMLElement>()\nfunction setTabRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) tabRefs.set(id, el)\n else tabRefs.delete(id)\n}\n\nconst { handleKeydown: handleTabArrows } = useRovingFocus({\n ids: () => props.modelValue.map(rack => rack.id),\n activeId: () => activeRack.value?.id,\n activate: selectRack,\n getElement: id => tabRefs.get(id),\n})\n\nconst reorder = useListReorder<Rack>({\n items: () => props.modelValue,\n getId: rack => rack.id,\n getLabel: rack => rack.name,\n disabled: () => !config.value.reorder || props.readonly,\n getElement: id => tabRefs.get(id),\n itemNoun: 'Plate',\n onReorder: ({ fromIndex, toIndex }) => {\n emit('rack-reorder', reorderList(props.modelValue.map(rack => rack.id), fromIndex, toIndex))\n },\n})\n\nfunction handleTabKeydown(event: KeyboardEvent, rack: Rack, index: number) {\n if (event.altKey) reorder.onKeydown(event, rack, index)\n else handleTabArrows(event)\n}\n\nconst canReorder = computed(() => config.value.reorder && !props.readonly)\nconst canAdd = computed(() => !props.readonly && props.modelValue.length < config.value.maxRacks)\nconst canRemove = computed(() => !props.readonly && props.modelValue.length > config.value.minRacks)\n\n// --- Toolbar ---\nconst formatOptions = computed(() => config.value.formats.map(format => ({ value: format, label: String(format) })))\nconst slotOptions = computed<readonly SlotPosition[]>(() =>\n config.value.slots === true ? SLOT_ORDER : config.value.slots || [],\n)\nconst dragMode = ref(false)\nconst confirmOpen = ref(false)\nconst isEmpty = computed(() => !activeRack.value || filledCount(activeRack.value) === 0)\nconst isFull = computed(() => {\n const rack = activeRack.value\n if (!rack) return true\n const { rows, cols } = PLATE_CONFIGS[rack.format]\n return filledCount(rack) >= rows * cols\n})\n\nconst showToolbar = computed(() => !props.readonly && Boolean(activeRack.value))\n\nfunction toggleDrag() {\n dragMode.value = !dragMode.value\n setSelection([])\n}\n\nfunction requestClear() {\n const rack = activeRack.value\n if (!rack) return\n if (config.value.clear === 'confirm') confirmOpen.value = true\n else emit('clear', rack.id)\n}\n\nfunction confirmClear() {\n confirmOpen.value = false\n if (activeRack.value) emit('clear', activeRack.value.id)\n}\n\nconst fileInput = ref<HTMLInputElement | null>(null)\nfunction handleFile(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n if (file) emit('import', file)\n input.value = ''\n}\n\nconst importFile = computed(() => config.value.import && config.value.importMode !== 'paste')\nconst importPaste = computed(() => config.value.import && config.value.importMode !== 'file')\nconst exportFormats = computed(() => config.value.export || [])\nconst ioLabel = computed(() => {\n if (!exportFormats.value.length) return 'Import'\n return config.value.import ? 'Import / Export' : 'Export'\n})\nconst ioItems = computed<ActionMenuItem[]>(() => [\n ...(importFile.value ? [{ id: 'import', label: 'Import file…' }] : []),\n ...(importPaste.value ? [{ id: 'paste', label: 'Paste…' }] : []),\n ...exportFormats.value.map(format => ({ id: `export:${format}`, label: `Export ${format.toUpperCase()}` })),\n])\n\nconst pasteOpen = ref(false)\nconst pasteText = ref('')\nfunction confirmPaste() {\n if (pasteText.value.trim()) emit('import-text', pasteText.value)\n pasteOpen.value = false\n}\n\nfunction handleIo(item: ActionMenuItem) {\n if (item.id === 'import') fileInput.value?.click()\n else if (item.id === 'paste') {\n // Every Paste starts empty, so text from a canceled dialog is never imported.\n pasteText.value = ''\n pasteOpen.value = true\n }\n else emit('export', item.id.slice('export:'.length))\n}\n\nconst showHistory = computed(() => !props.readonly && (config.value.history || config.value.clear === 'undo'))\n\n// Scoped to the editor, and text fields keep their own undo.\nfunction handleRootKeydown(event: KeyboardEvent) {\n if (!showHistory.value || !(event.ctrlKey || event.metaKey)) return\n if ((event.target as HTMLElement).closest('input, textarea, select, [contenteditable=\"true\"]')) return\n const key = event.key.toLowerCase()\n const redo = key === 'y' || (key === 'z' && event.shiftKey)\n if (redo ? !props.canRedo : key !== 'z' || !props.canUndo) return\n event.preventDefault()\n if (redo) emit('redo')\n else emit('undo')\n}\n\n// A focused Undo/Redo that turns disabled would drop focus to <body>, out of reach of\n// the editor's shortcuts; hand it to the editor root first (pre-flush, before the patch).\nwatch(() => [props.canUndo, props.canRedo], () => {\n const step = (document.activeElement as HTMLElement | null)?.dataset?.history\n if ((step === 'undo' && !props.canUndo) || (step === 'redo' && !props.canRedo)) rootRef.value?.focus()\n})\n\n// --- Plate ---\nconst groupList = computed(() => config.value.groups || [])\nconst groupColor = (group: SampleType, index: number) =>\n group.color ?? SAMPLE_GROUP_TOKENS[index % SAMPLE_GROUP_TOKENS.length]\n\nconst sampleColors = computed<Record<string, string>>(() =>\n Object.fromEntries(groupList.value.map((group, index) => [group.id, groupColor(group, index)])),\n)\nconst legendItems = computed<WellLegendItem[] | undefined>(() =>\n config.value.groups\n ? config.value.groups.map((group, index) => ({ type: group.id, label: group.name, color: groupColor(group, index) }))\n : undefined,\n)\n\n// --- Rail: measured, because the popover it replaces is teleported out of reach of CSS ---\nconst RAIL_MIN_WIDTH = 720\n// __main's 2×20px side padding plus the 232px rail column.\nconst RAIL_CHROME = 40 + 232\nconst rootRef = ref<HTMLElement | null>(null)\nconst railRef = ref<HTMLElement | null>(null)\nconst width = ref(0)\nlet resizeObserver: ResizeObserver | undefined\n\nonMounted(() => {\n const root = rootRef.value\n if (!root) return\n width.value = root.getBoundingClientRect().width\n if (typeof ResizeObserver === 'undefined') return\n resizeObserver = new ResizeObserver(([entry]) => { if (entry) width.value = entry.contentRect.width })\n resizeObserver.observe(root)\n})\nonBeforeUnmount(() => resizeObserver?.disconnect())\n\n// ponytail: plate width from the size table (table border-spacing counted per column); 'fill' counts as 0.\nconst plateWidth = computed(() => {\n const { cols } = PLATE_CONFIGS[activeRack.value?.format ?? 96]\n const size = WELL_PLATE_SIZE_CONFIGS[config.value.size]\n const px = (value: string) => (value.endsWith('px') ? parseFloat(value) : 0)\n return px(size.headerWidth) + cols * px(size.cellWidth) + (cols + 2) * px(size.gap)\n})\n// The rail waits until the plate still fits beside it, so it never forces a sideways scroll.\nconst railOn = computed(() => width.value >= Math.max(RAIL_MIN_WIDTH, plateWidth.value + RAIL_CHROME))\n\n/** Rail list: the groups, or the sample types when there are none. */\nconst railItems = computed(() => {\n const wells = Object.values(activeRack.value?.wells ?? {})\n const items = legendItems.value ?? DEFAULT_WELL_LEGEND_ITEMS\n return items.map(item => ({\n ...item,\n count: wells.filter(well => (config.value.groups ? well.group : well.sampleType) === item.type).length,\n swatch: config.value.groups ? { backgroundColor: item.color } : sampleTypeMarker(item.type, item.color).style,\n glyph: config.value.groups ? '' : sampleTypeMarker(item.type, item.color).glyph,\n }))\n})\n\nconst inspectorOn = computed(() => Boolean(config.value.wellFields) && !props.readonly && !dragMode.value)\n\nconst selectionMode = computed<WellPlateSelectionMode>(() => {\n if (props.readonly) return 'none'\n if (dragMode.value) return 'drag'\n // Without groups the selection only feeds the inspector; with neither, nothing acts on it.\n if (!config.value.groups && !inspectorOn.value) return 'none'\n // A Ctrl/Cmd-click toggles a well in every mode. Beside the rail a plain click selects\n // one well and the inspector opens there; in the popover mode it opens the popover.\n // Groups add the rectangle drag, except where a plain click edits.\n return config.value.groups && (railOn.value || !inspectorOn.value) ? 'rectangle' : 'multiple'\n})\n\nconst dropParser = computed<WellSampleDropParser | undefined>(() => {\n if (typeof config.value.sampleDrop === 'function') return config.value.sampleDrop\n return config.value.sampleDrop ? event => (event.dataTransfer ? parseDroppedSampleJson(event.dataTransfer) : null) : undefined\n})\n\nconst plateSpec = computed(() => defineWellPlate({\n format: activeRack.value?.format,\n selectionMode: selectionMode.value,\n editable: inspectorOn.value && !railOn.value,\n editFields: config.value.wellFields || undefined,\n defaultInjectionVolume: activeRack.value?.injectionVolume,\n size: config.value.size,\n sampleColors: sampleColors.value,\n legendItems: legendItems.value,\n allowSampleDrop: Boolean(config.value.sampleDrop) && !props.readonly,\n sampleDropParser: dropParser.value,\n showWellLabels: config.value.size !== 'xs',\n showBadges: true,\n showLegend: !railOn.value,\n}))\n\nconst selection = ref<string[]>([])\nfunction setSelection(wellIds: string[]) {\n selection.value = wellIds\n emit('selection-change', wellIds)\n}\n// Well ids from another plate or format would be ghosts, and a mode that cannot select\n// could never clear them; drop them and tell the parent.\nwatch(\n [() => activeRack.value?.id, () => activeRack.value?.format, () => selectionMode.value === 'none'],\n () => { if (selection.value.length) setSelection([]) },\n)\n\n// Without groups a selection beside the rail is the inspected well; it closes with the rail\n// instead of reopening, and stealing focus, when the rail comes back.\nwatch(railOn, () => { if (!config.value.groups && selection.value.length) setSelection([]) })\n\nfunction handleSelection(wellIds: string[]) {\n if (selectionMode.value === 'none' || dragMode.value) return\n setSelection(wellIds)\n}\n\nfunction handleWellEdit(wellId: string, data: WellEditData) {\n const rack = activeRack.value\n if (!rack) return\n const fields = config.value.wellFields || []\n // An unnamed sample is an empty well, unless its group still names it; blanks and QCs stand alone.\n const grouped = groupList.value.some(group => group.id === rack.wells[wellId]?.group)\n const sample = !data.sampleType || data.sampleType === 'sample'\n if (fields.includes('label') && !data.label && sample && !grouped) emit('well-clear', rack.id, wellId)\n else emit('well-edit', rack.id, wellId, data)\n}\n\nconst railWellId = computed(() =>\n railOn.value && inspectorOn.value && selection.value.length === 1 ? selection.value[0] : undefined,\n)\nconst railGroupLabel = computed(() => {\n const groupId = railWellId.value && activeRack.value?.wells[railWellId.value]?.group\n return groupList.value.find(group => group.id === groupId)?.name\n})\nconst railPosition = () => {\n const rect = railRef.value?.getBoundingClientRect()\n return { x: rect?.left ?? 0, y: rect?.top ?? 0 }\n}\n\n// Like the popover's Clear, the rail's closes the inspector and hands focus back to the well.\nfunction clearRailWell() {\n emit('well-clear', activeRack.value!.id, railWellId.value!)\n setSelection([])\n}\n\nfunction assign(groupId: string | undefined) {\n if (!activeRack.value) return\n emit('assign', activeRack.value.id, selection.value, groupId)\n setSelection([])\n}\n\nconst hint = computed(() => {\n if (dragMode.value) return 'Drag a filled well onto another to move or swap it.'\n if (props.hint) return props.hint\n if (!config.value.groups) return ''\n if (railOn.value) {\n return inspectorOn.value\n ? 'Click a well to edit it on the right. Drag across wells, Ctrl-click to add one or Shift-click for a range.'\n : 'Click or drag across wells to select them.'\n }\n return inspectorOn.value\n ? 'Click a well to edit it. Ctrl- or Cmd-click to select several, Shift-click for a range.'\n : 'Click or drag across wells to select them.'\n})\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"['mint-plate-editor', { 'mint-plate-editor--readonly': readonly, 'mint-plate-editor--rail': railOn }]\"\n tabindex=\"-1\"\n @keydown=\"handleRootKeydown\"\n >\n <!-- Plate tabs -->\n <div class=\"mint-plate-editor__tabbar\">\n <div class=\"mint-plate-editor__tabs\" role=\"tablist\" aria-label=\"Plates\">\n <!-- Tab and close button stay siblings; the item carries the shared underline. -->\n <span\n v-for=\"(rack, index) in modelValue\"\n :key=\"rack.id\"\n role=\"presentation\"\n :class=\"['mint-plate-editor__tab-item', { 'mint-plate-editor__tab-item--active': rack.id === activeRack?.id }]\"\n >\n <button\n :ref=\"(el) => setTabRef(rack.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"rack.id === activeRack?.id\"\n :tabindex=\"rack.id === activeRack?.id ? 0 : -1\"\n :draggable=\"canReorder\"\n :title=\"canReorder ? `${rack.name} — Alt with ← / → reorders` : undefined\"\n :class=\"[\n 'mint-plate-editor__tab',\n {\n 'mint-plate-editor__tab--dragging': reorder.draggingId.value === rack.id,\n 'mint-plate-editor__tab--drag-over': reorder.dragOverId.value === rack.id,\n },\n ]\"\n @click=\"selectRack(rack.id)\"\n @keydown=\"handleTabKeydown($event, rack, index)\"\n @dragstart=\"reorder.onDragStart($event, rack, index)\"\n @dragover=\"reorder.onDragOver($event, rack, index)\"\n @dragleave=\"reorder.onDragLeave\"\n @drop=\"reorder.onDrop($event, rack, index)\"\n @dragend=\"reorder.onDragEnd\"\n >\n <span :class=\"['mint-plate-editor__slot-dot', `mint-plate-editor__slot-dot--${rack.slot.toLowerCase()}`]\" aria-hidden=\"true\" />\n {{ rack.name }}\n <span class=\"mint-plate-editor__tab-count\">{{ filledCount(rack) }}</span>\n </button>\n <button\n v-if=\"rack.id === activeRack?.id && canRemove\"\n type=\"button\"\n class=\"mint-plate-editor__tab-close\"\n :aria-label=\"`Remove ${rack.name}`\"\n :title=\"`Remove ${rack.name}`\"\n @click=\"emit('rack-remove', rack.id)\"\n >\n <X :size=\"12\" aria-hidden=\"true\" />\n </button>\n </span>\n </div>\n\n <BaseButton\n v-if=\"canAdd\"\n class=\"mint-plate-editor__add\"\n variant=\"ghost\"\n size=\"sm\"\n :icon=\"Plus\"\n label=\"Add plate\"\n @click=\"emit('rack-add')\"\n />\n\n <div class=\"mint-plate-editor__tabbar-end\">\n <template v-if=\"showHistory\">\n <BaseButton variant=\"ghost\" size=\"sm\" :icon=\"Undo2\" label=\"Undo\" title=\"Undo (Ctrl+Z)\" data-history=\"undo\" :disabled=\"!canUndo\" @click=\"emit('undo')\" />\n <BaseButton variant=\"ghost\" size=\"sm\" :icon=\"Redo2\" label=\"Redo\" title=\"Redo (Ctrl+Shift+Z)\" data-history=\"redo\" :disabled=\"!canRedo\" @click=\"emit('redo')\" />\n </template>\n <template v-if=\"!readonly && (config.import || exportFormats.length)\">\n <span v-if=\"showHistory\" class=\"mint-plate-editor__divider\" aria-hidden=\"true\" />\n <ActionMenu v-if=\"exportFormats.length || ioItems.length > 1\" :label=\"ioLabel\" :items=\"ioItems\" @select=\"handleIo\">\n <template #trigger=\"{ open, toggle, keydown }\">\n <button\n type=\"button\"\n class=\"mint-plate-editor__action\"\n data-action=\"import-export\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"open\"\n @click=\"toggle\"\n @keydown=\"keydown\"\n ><component :is=\"exportFormats.length ? Download : Upload\" :size=\"14\" aria-hidden=\"true\" />{{ ioLabel }}</button>\n </template>\n </ActionMenu>\n <button v-else type=\"button\" class=\"mint-plate-editor__action\" @click=\"handleIo(ioItems[0])\">\n <Upload :size=\"14\" aria-hidden=\"true\" />\n Import\n </button>\n <input v-if=\"importFile\" ref=\"fileInput\" type=\"file\" class=\"mint-plate-editor__file\" :accept=\"config.importAccept\" tabindex=\"-1\" aria-hidden=\"true\" @change=\"handleFile\">\n </template>\n <slot name=\"tab-extra\" />\n </div>\n </div>\n\n <!-- Toolbar for the active plate -->\n <div v-if=\"showToolbar && activeRack\" class=\"mint-plate-editor__toolbar\">\n <div v-if=\"config.formats.length > 1\" class=\"mint-plate-editor__field mint-plate-editor__field--format\">\n <span class=\"mint-plate-editor__label\">Format</span>\n <SegmentedControl\n :model-value=\"activeRack.format\"\n :options=\"formatOptions\"\n size=\"sm\"\n aria-label=\"Format\"\n @update:model-value=\"emit('update:format', activeRack.id, Number($event) as WellPlateFormat)\"\n />\n </div>\n\n <div v-if=\"slotOptions.length\" class=\"mint-plate-editor__field\">\n <span class=\"mint-plate-editor__label\">Slot</span>\n <div class=\"mint-plate-editor__slots\" role=\"group\" aria-label=\"Slot\">\n <button\n v-for=\"slot in slotOptions\"\n :key=\"slot\"\n type=\"button\"\n :aria-pressed=\"activeRack.slot === slot\"\n :class=\"[\n 'mint-plate-editor__slot',\n `mint-plate-editor__slot--${slot.toLowerCase()}`,\n { 'mint-plate-editor__slot--active': activeRack.slot === slot },\n ]\"\n @click=\"emit('update:slot', activeRack.id, slot)\"\n >\n {{ slot }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-plate-editor__toolbar-end\">\n <button\n v-if=\"config.fillSeries\"\n type=\"button\"\n class=\"mint-plate-editor__action\"\n data-action=\"fill-series\"\n :disabled=\"isFull && config.fillSeriesWhenFull === 'disable'\"\n :title=\"isFull ? 'Plate is full' : 'Name every empty well in order'\"\n @click=\"emit('fill-series', activeRack.id)\"\n >\n Fill Series\n </button>\n <button\n v-if=\"config.wellDrag\"\n type=\"button\"\n :class=\"['mint-plate-editor__action', { 'mint-plate-editor__action--on': dragMode }]\"\n data-action=\"drag\"\n :aria-pressed=\"dragMode\"\n title=\"Drag wells to move or swap them\"\n @click=\"toggleDrag\"\n >\n <Move :size=\"14\" aria-hidden=\"true\" />\n Drag\n </button>\n <button\n v-if=\"config.clear\"\n type=\"button\"\n class=\"mint-plate-editor__action mint-plate-editor__action--danger\"\n :disabled=\"isEmpty\"\n title=\"Clear every well on this plate\"\n @click=\"requestClear\"\n >\n <Trash2 :size=\"14\" aria-hidden=\"true\" />\n Clear\n </button>\n <slot name=\"toolbar-extra\" />\n </div>\n </div>\n\n <!-- Plate -->\n <div v-if=\"activeRack\" class=\"mint-plate-editor__body\">\n <div class=\"mint-plate-editor__main\">\n <!-- Keyed by rack: the Shift-click anchor and an open popover belong to one plate. -->\n <WellPlate\n :key=\"activeRack.id\"\n :model-value=\"selection\"\n :spec=\"plateSpec\"\n :wells=\"activeRack.wells\"\n :readonly=\"readonly\"\n @well-click=\"(wellId: string) => emit('well-click', activeRack!.id, wellId)\"\n @selection-change=\"handleSelection\"\n @well-edit=\"handleWellEdit\"\n @well-clear=\"(wellId: string) => emit('well-clear', activeRack!.id, wellId)\"\n @well-move=\"(source: string, target: string) => emit('well-move', activeRack!.id, source, target)\"\n @sample-drop=\"(wellId: string, data: WellSampleDropData, event: DragEvent) => emit('sample-drop', activeRack!.id, wellId, data, event)\"\n >\n <template v-if=\"$slots['well-editor']\" #well-editor=\"slotProps\">\n <slot name=\"well-editor\" v-bind=\"slotProps\" :rack=\"activeRack\" :rack-id=\"activeRack.id\" :inline=\"false\" />\n </template>\n </WellPlate>\n\n <!-- Selection bar, or a hint while nothing is selected -->\n <div v-if=\"!readonly && (config.groups || config.wellDrag)\" class=\"mint-plate-editor__footer\">\n <div v-if=\"selection.length && config.groups\" class=\"mint-plate-editor__selection\" role=\"toolbar\" aria-label=\"Selected wells\">\n <span class=\"mint-plate-editor__selection-count\">\n <span class=\"mint-plate-editor__mono\">{{ selection.length }}</span> selected\n </span>\n <span class=\"mint-plate-editor__divider\" aria-hidden=\"true\" />\n <span class=\"mint-plate-editor__label\">Group</span>\n <button\n v-for=\"(group, index) in config.groups\"\n :key=\"group.id\"\n type=\"button\"\n class=\"mint-plate-editor__group\"\n @click=\"assign(group.id)\"\n >\n <span class=\"mint-plate-editor__group-swatch\" :style=\"{ backgroundColor: groupColor(group, index) }\" aria-hidden=\"true\" />\n {{ group.name }}\n </button>\n <span class=\"mint-plate-editor__divider\" aria-hidden=\"true\" />\n <button\n type=\"button\"\n class=\"mint-plate-editor__icon-action mint-plate-editor__icon-action--danger\"\n aria-label=\"Clear selected wells\"\n title=\"Clear selected wells\"\n @click=\"assign(undefined)\"\n >\n <Trash2 :size=\"14\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"mint-plate-editor__icon-action\"\n aria-label=\"Cancel selection\"\n title=\"Cancel selection (Esc)\"\n @click=\"setSelection([])\"\n >\n <X :size=\"14\" aria-hidden=\"true\" />\n </button>\n </div>\n <span v-else-if=\"hint\" class=\"mint-plate-editor__hint\">{{ hint }}</span>\n </div>\n </div>\n\n <aside v-if=\"railOn\" ref=\"railRef\" class=\"mint-plate-editor__rail\" aria-label=\"Plate details\">\n <template v-if=\"railWellId\">\n <slot\n name=\"well-editor\"\n :rack=\"activeRack\"\n :rack-id=\"activeRack.id\"\n :well-id=\"railWellId\"\n :well-data=\"activeRack.wells[railWellId]\"\n :edit-fields=\"config.wellFields || []\"\n :default-injection-volume=\"activeRack.injectionVolume\"\n :position=\"railPosition()\"\n :save=\"(data: WellEditData) => handleWellEdit(railWellId!, data)\"\n :clear=\"clearRailWell\"\n :close=\"() => setSelection([])\"\n :inline=\"true\"\n >\n <WellEditPopupInternal\n inline\n :well-id=\"railWellId\"\n :well-data=\"activeRack.wells[railWellId]\"\n :edit-fields=\"config.wellFields || undefined\"\n :default-injection-volume=\"activeRack.injectionVolume\"\n :marker-colors=\"sampleColors\"\n :group-label=\"railGroupLabel\"\n :position=\"{ x: 0, y: 0 }\"\n @save=\"handleWellEdit(railWellId!, $event)\"\n @clear=\"clearRailWell\"\n @close=\"setSelection([])\"\n />\n </slot>\n </template>\n <template v-else>\n <span class=\"mint-plate-editor__rail-title\">{{ config.groups ? 'Groups' : 'Sample types' }}</span>\n <ul class=\"mint-plate-editor__rail-list\">\n <li v-for=\"item in railItems\" :key=\"item.type\" class=\"mint-plate-editor__rail-row\">\n <span\n :class=\"['mint-plate-editor__rail-swatch', { 'mint-plate-editor__rail-swatch--marker': !config.groups }]\"\n :style=\"item.swatch\"\n aria-hidden=\"true\"\n >{{ item.glyph }}</span>\n <span class=\"mint-plate-editor__rail-name\">{{ item.label }}</span>\n <span class=\"mint-plate-editor__rail-count\">{{ item.count }}</span>\n </li>\n </ul>\n <button\n v-if=\"config.groups && !readonly\"\n type=\"button\"\n class=\"mint-plate-editor__rail-add\"\n data-action=\"group-add\"\n @click=\"emit('group-add')\"\n >\n <Plus :size=\"12\" aria-hidden=\"true\" />\n New group\n </button>\n </template>\n </aside>\n </div>\n\n <ConfirmDialog\n v-if=\"config.clear === 'confirm'\"\n v-model:open=\"confirmOpen\"\n :title=\"config.clearConfirm?.title ?? 'Clear plate'\"\n :message=\"config.clearConfirm?.message ?? `Clear all ${activeRack ? filledCount(activeRack) : 0} wells on ${activeRack?.name ?? 'this plate'}?`\"\n :confirm-label=\"config.clearConfirm?.confirmLabel ?? 'Clear'\"\n :tone=\"config.clearConfirm?.tone\"\n @confirm=\"confirmClear\"\n />\n\n <ConfirmDialog\n v-if=\"importPaste\"\n v-model:open=\"pasteOpen\"\n title=\"Paste wells\"\n message=\"Paste the rows to import, e.g. copied from a spreadsheet.\"\n confirm-label=\"Import\"\n tone=\"info\"\n @confirm=\"confirmPaste\"\n >\n <BaseInput type=\"textarea\" v-model=\"pasteText\" :rows=\"8\" aria-label=\"Pasted rows\" />\n </ConfirmDialog>\n\n <div role=\"status\" aria-live=\"polite\" :style=\"reorder.announcerStyle\">{{ reorder.announcement.value }}</div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Multi-plate editor over WellPlate: plate tabs, format and slot, a single-well\n * inspector, group assignment, well drag and sample drop.\n *\n * At 720px and wider a right rail lists the groups (or sample types) and holds\n * the inspector for a single selected well; narrower, the rail collapses and\n * the inspector opens as a popover.\n *\n * Controlled: it never changes `modelValue`. Every action is emitted as an\n * intent (`well-edit`, `assign`, `rack-add`, …) for the caller to apply — or\n * bind `useRackEditor().plateEditorListeners` to let the SDK hold the state.\n *\n * @example\n * ```vue\n * <PlateEditor v-model:active-rack=\"activeId\" :spec=\"definePlateEditor({ slots: true, fillSeries: true })\" :model-value=\"racks\" @well-edit=\"save\" />\n * ```\n */\nimport { computed, onBeforeUnmount, onMounted, ref, watch, type ComponentPublicInstance } from 'vue'\nimport { Download, Move, Plus, Redo2, Trash2, Undo2, Upload, X } from 'lucide-vue-next'\nimport type {\n ActionMenuItem,\n PlateEditorSpec,\n Rack,\n SampleType,\n SlotPosition,\n Well,\n WellEditData,\n WellEditField,\n WellLegendItem,\n WellPlateFormat,\n WellPlateSelectionMode,\n WellSampleDropData,\n WellSampleDropParser,\n} from '../types'\nimport { SAMPLE_GROUP_TOKENS, SLOT_ORDER } from '../utils/color'\nimport { reorderList, useListReorder } from '../composables/useListReorder'\nimport { useRovingFocus } from '../composables/useRovingFocus'\nimport { PLATE_CONFIGS, WELL_PLATE_SIZE_CONFIGS } from './WellPlate.geometry'\nimport { parseDroppedSampleJson } from './WellPlate.sampleDrop'\nimport { DEFAULT_WELL_LEGEND_ITEMS } from './WellPlate.legend'\nimport { applySpecDefaults, defineWellPlate } from '../composables/componentSpecs'\nimport { sampleTypeMarker } from '../utils/sampleTypeMarker'\nimport WellPlate from './WellPlate.vue'\nimport WellEditPopupInternal from './internal/WellEditPopupInternal.vue'\nimport ActionMenu from './ActionMenu.vue'\nimport BaseButton from './BaseButton.vue'\nimport SegmentedControl from './SegmentedControl.vue'\nimport ConfirmDialog from './ConfirmDialog.vue'\nimport BaseInput from './BaseInput.vue'\n\ninterface Props {\n /** Structure built with definePlateEditor(). Optional: every option has a default. */\n spec?: PlateEditorSpec\n /** The plates. Never mutated; changes arrive as emitted intents. */\n modelValue: Rack[]\n /** Id of the active plate. Bind with `v-model:active-rack`; unbound, the tabs keep their own choice. */\n activeRack?: string\n canUndo?: boolean\n canRedo?: boolean\n /** No editing controls; well clicks still emit `well-click`. */\n readonly?: boolean\n /** Footer text while nothing is selected; replaces the built-in selection hint. */\n hint?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n spec: () => ({}),\n activeRack: undefined,\n canUndo: false,\n canRedo: false,\n readonly: false,\n})\n\nconst config = computed(() => applySpecDefaults({\n formats: [54, 96],\n slots: false,\n reorder: true,\n wellDrag: false,\n groups: false,\n wellFields: ['label', 'sampleType', 'injectionVolume', 'injectionCount', 'customMethod'],\n sampleDrop: false,\n import: false,\n importMode: 'file',\n export: false,\n clear: 'confirm',\n history: false,\n fillSeries: false,\n fillSeriesWhenFull: 'disable',\n maxRacks: 10,\n minRacks: 1,\n size: 'xs',\n}, props.spec))\n\nconst emit = defineEmits<{\n 'update:activeRack': [rackId: string]\n 'rack-add': []\n 'rack-remove': [rackId: string]\n 'rack-reorder': [rackIds: string[]]\n 'update:format': [rackId: string, format: WellPlateFormat]\n 'update:slot': [rackId: string, slot: SlotPosition]\n 'well-click': [rackId: string, wellId: string]\n 'well-edit': [rackId: string, wellId: string, data: WellEditData]\n 'well-clear': [rackId: string, wellId: string]\n 'well-move': [rackId: string, sourceWellId: string, targetWellId: string]\n 'sample-drop': [rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent]\n /** `groupId` undefined clears the wells. */\n 'assign': [rackId: string, wellIds: string[], groupId: string | undefined]\n 'clear': [rackId: string]\n 'fill-series': [rackId: string]\n 'import': [file: File]\n /** The pasted text, unparsed. */\n 'import-text': [text: string]\n 'export': [format: string]\n 'group-add': []\n 'undo': []\n 'redo': []\n 'selection-change': [wellIds: string[]]\n}>()\n\ninterface PlateEditorWellEditorSlotProps {\n rack: Rack\n rackId: string\n wellId: string\n wellData?: Partial<Well>\n editFields: WellEditField[]\n defaultInjectionVolume: number\n position: { x: number; y: number }\n save: (data: WellEditData) => void\n clear: () => void\n close: () => void\n /** True in the rail, false in the popover. */\n inline: boolean\n}\n\ndefineSlots<{\n /** Replaces the default single-well inspector, in the rail or the popover. */\n 'well-editor'?: (props: PlateEditorWellEditorSlotProps) => unknown\n /** Extra controls at the right end of the tab bar. */\n 'tab-extra'?: () => unknown\n /** Extra controls at the right end of the toolbar. */\n 'toolbar-extra'?: () => unknown\n}>()\n\n// --- Active plate: a bound activeRack wins; unbound, the tabs keep their own choice ---\nconst localActiveId = ref<string>()\n\nconst activeRack = computed<Rack | undefined>(() => {\n const id = props.activeRack ?? localActiveId.value\n return props.modelValue.find(rack => rack.id === id) ?? props.modelValue[0]\n})\n\nfunction selectRack(rackId: string) {\n localActiveId.value = rackId\n emit('update:activeRack', rackId)\n}\n\nfunction filledCount(rack: Rack): number {\n return Object.values(rack.wells).filter(well => well.sampleType).length\n}\n\n// --- Tabs: roving focus, Alt+←/→ and drag reorder ---\nconst tabRefs = new Map<string, HTMLElement>()\nfunction setTabRef(id: string, el: Element | ComponentPublicInstance | null) {\n if (el instanceof HTMLElement) tabRefs.set(id, el)\n else tabRefs.delete(id)\n}\n\nconst { handleKeydown: handleTabArrows } = useRovingFocus({\n ids: () => props.modelValue.map(rack => rack.id),\n activeId: () => activeRack.value?.id,\n activate: selectRack,\n getElement: id => tabRefs.get(id),\n})\n\nconst reorder = useListReorder<Rack>({\n items: () => props.modelValue,\n getId: rack => rack.id,\n getLabel: rack => rack.name,\n disabled: () => !config.value.reorder || props.readonly,\n getElement: id => tabRefs.get(id),\n itemNoun: 'Plate',\n onReorder: ({ fromIndex, toIndex }) => {\n emit('rack-reorder', reorderList(props.modelValue.map(rack => rack.id), fromIndex, toIndex))\n },\n})\n\nfunction handleTabKeydown(event: KeyboardEvent, rack: Rack, index: number) {\n if (event.altKey) reorder.onKeydown(event, rack, index)\n else handleTabArrows(event)\n}\n\nconst canReorder = computed(() => config.value.reorder && !props.readonly)\nconst canAdd = computed(() => !props.readonly && props.modelValue.length < config.value.maxRacks)\nconst canRemove = computed(() => !props.readonly && props.modelValue.length > config.value.minRacks)\n\n// --- Toolbar ---\nconst formatOptions = computed(() => config.value.formats.map(format => ({ value: format, label: String(format) })))\nconst slotOptions = computed<readonly SlotPosition[]>(() =>\n config.value.slots === true ? SLOT_ORDER : config.value.slots || [],\n)\nconst dragMode = ref(false)\nconst confirmOpen = ref(false)\nconst isEmpty = computed(() => !activeRack.value || filledCount(activeRack.value) === 0)\nconst isFull = computed(() => {\n const rack = activeRack.value\n if (!rack) return true\n const { rows, cols } = PLATE_CONFIGS[rack.format]\n return filledCount(rack) >= rows * cols\n})\n\nconst showToolbar = computed(() => !props.readonly && Boolean(activeRack.value))\n\nfunction toggleDrag() {\n dragMode.value = !dragMode.value\n setSelection([])\n}\n\nfunction requestClear() {\n const rack = activeRack.value\n if (!rack) return\n if (config.value.clear === 'confirm') confirmOpen.value = true\n else emit('clear', rack.id)\n}\n\nfunction confirmClear() {\n confirmOpen.value = false\n if (activeRack.value) emit('clear', activeRack.value.id)\n}\n\nconst fileInput = ref<HTMLInputElement | null>(null)\nfunction handleFile(event: Event) {\n const input = event.target as HTMLInputElement\n const file = input.files?.[0]\n if (file) emit('import', file)\n input.value = ''\n}\n\nconst importFile = computed(() => config.value.import && config.value.importMode !== 'paste')\nconst importPaste = computed(() => config.value.import && config.value.importMode !== 'file')\nconst exportFormats = computed(() => config.value.export || [])\nconst ioLabel = computed(() => {\n if (!exportFormats.value.length) return 'Import'\n return config.value.import ? 'Import / Export' : 'Export'\n})\nconst ioItems = computed<ActionMenuItem[]>(() => [\n ...(importFile.value ? [{ id: 'import', label: 'Import file…' }] : []),\n ...(importPaste.value ? [{ id: 'paste', label: 'Paste…' }] : []),\n ...exportFormats.value.map(format => ({ id: `export:${format}`, label: `Export ${format.toUpperCase()}` })),\n])\n\nconst pasteOpen = ref(false)\nconst pasteText = ref('')\nfunction confirmPaste() {\n if (pasteText.value.trim()) emit('import-text', pasteText.value)\n pasteOpen.value = false\n}\n\nfunction handleIo(item: ActionMenuItem) {\n if (item.id === 'import') fileInput.value?.click()\n else if (item.id === 'paste') {\n // Every Paste starts empty, so text from a canceled dialog is never imported.\n pasteText.value = ''\n pasteOpen.value = true\n }\n else emit('export', item.id.slice('export:'.length))\n}\n\nconst showHistory = computed(() => !props.readonly && (config.value.history || config.value.clear === 'undo'))\n\n// Scoped to the editor, and text fields keep their own undo.\nfunction handleRootKeydown(event: KeyboardEvent) {\n if (!showHistory.value || !(event.ctrlKey || event.metaKey)) return\n if ((event.target as HTMLElement).closest('input, textarea, select, [contenteditable=\"true\"]')) return\n const key = event.key.toLowerCase()\n const redo = key === 'y' || (key === 'z' && event.shiftKey)\n if (redo ? !props.canRedo : key !== 'z' || !props.canUndo) return\n event.preventDefault()\n if (redo) emit('redo')\n else emit('undo')\n}\n\n// A focused Undo/Redo that turns disabled would drop focus to <body>, out of reach of\n// the editor's shortcuts; hand it to the editor root first (pre-flush, before the patch).\nwatch(() => [props.canUndo, props.canRedo], () => {\n const step = (document.activeElement as HTMLElement | null)?.dataset?.history\n if ((step === 'undo' && !props.canUndo) || (step === 'redo' && !props.canRedo)) rootRef.value?.focus()\n})\n\n// --- Plate ---\nconst groupList = computed(() => config.value.groups || [])\nconst groupColor = (group: SampleType, index: number) =>\n group.color ?? SAMPLE_GROUP_TOKENS[index % SAMPLE_GROUP_TOKENS.length]\n\nconst sampleColors = computed<Record<string, string>>(() =>\n Object.fromEntries(groupList.value.map((group, index) => [group.id, groupColor(group, index)])),\n)\nconst legendItems = computed<WellLegendItem[] | undefined>(() =>\n config.value.groups\n ? config.value.groups.map((group, index) => ({ type: group.id, label: group.name, color: groupColor(group, index) }))\n : undefined,\n)\n\n// --- Rail: measured, because the popover it replaces is teleported out of reach of CSS ---\nconst RAIL_MIN_WIDTH = 720\n// __main's 2×20px side padding plus the 232px rail column.\nconst RAIL_CHROME = 40 + 232\nconst rootRef = ref<HTMLElement | null>(null)\nconst railRef = ref<HTMLElement | null>(null)\nconst width = ref(0)\nlet resizeObserver: ResizeObserver | undefined\n\nonMounted(() => {\n const root = rootRef.value\n if (!root) return\n width.value = root.getBoundingClientRect().width\n if (typeof ResizeObserver === 'undefined') return\n resizeObserver = new ResizeObserver(([entry]) => { if (entry) width.value = entry.contentRect.width })\n resizeObserver.observe(root)\n})\nonBeforeUnmount(() => resizeObserver?.disconnect())\n\n// ponytail: plate width from the size table (table border-spacing counted per column); 'fill' counts as 0.\nconst plateWidth = computed(() => {\n const { cols } = PLATE_CONFIGS[activeRack.value?.format ?? 96]\n const size = WELL_PLATE_SIZE_CONFIGS[config.value.size]\n const px = (value: string) => (value.endsWith('px') ? parseFloat(value) : 0)\n return px(size.headerWidth) + cols * px(size.cellWidth) + (cols + 2) * px(size.gap)\n})\n// The rail waits until the plate still fits beside it, so it never forces a sideways scroll.\nconst railOn = computed(() => width.value >= Math.max(RAIL_MIN_WIDTH, plateWidth.value + RAIL_CHROME))\n\n/** Rail list: the groups, or the sample types when there are none. */\nconst railItems = computed(() => {\n const wells = Object.values(activeRack.value?.wells ?? {})\n const items = legendItems.value ?? DEFAULT_WELL_LEGEND_ITEMS\n return items.map(item => ({\n ...item,\n count: wells.filter(well => (config.value.groups ? well.group : well.sampleType) === item.type).length,\n swatch: config.value.groups ? { backgroundColor: item.color } : sampleTypeMarker(item.type, item.color).style,\n glyph: config.value.groups ? '' : sampleTypeMarker(item.type, item.color).glyph,\n }))\n})\n\nconst inspectorOn = computed(() => Boolean(config.value.wellFields) && !props.readonly && !dragMode.value)\n\nconst selectionMode = computed<WellPlateSelectionMode>(() => {\n if (props.readonly) return 'none'\n if (dragMode.value) return 'drag'\n // Without groups the selection only feeds the inspector; with neither, nothing acts on it.\n if (!config.value.groups && !inspectorOn.value) return 'none'\n // A Ctrl/Cmd-click toggles a well in every mode. Beside the rail a plain click selects\n // one well and the inspector opens there; in the popover mode it opens the popover.\n // Groups add the rectangle drag, except where a plain click edits.\n return config.value.groups && (railOn.value || !inspectorOn.value) ? 'rectangle' : 'multiple'\n})\n\nconst dropParser = computed<WellSampleDropParser | undefined>(() => {\n if (typeof config.value.sampleDrop === 'function') return config.value.sampleDrop\n return config.value.sampleDrop ? event => (event.dataTransfer ? parseDroppedSampleJson(event.dataTransfer) : null) : undefined\n})\n\nconst plateSpec = computed(() => defineWellPlate({\n format: activeRack.value?.format,\n selectionMode: selectionMode.value,\n editable: inspectorOn.value && !railOn.value,\n editFields: config.value.wellFields || undefined,\n defaultInjectionVolume: activeRack.value?.injectionVolume,\n size: config.value.size,\n sampleColors: sampleColors.value,\n legendItems: legendItems.value,\n allowSampleDrop: Boolean(config.value.sampleDrop) && !props.readonly,\n sampleDropParser: dropParser.value,\n showWellLabels: config.value.size !== 'xs',\n showBadges: true,\n showLegend: !railOn.value,\n}))\n\nconst selection = ref<string[]>([])\nfunction setSelection(wellIds: string[]) {\n selection.value = wellIds\n emit('selection-change', wellIds)\n}\n// Well ids from another plate or format would be ghosts, and a mode that cannot select\n// could never clear them; drop them and tell the parent.\nwatch(\n [() => activeRack.value?.id, () => activeRack.value?.format, () => selectionMode.value === 'none'],\n () => { if (selection.value.length) setSelection([]) },\n)\n\n// Without groups a selection beside the rail is the inspected well; it closes with the rail\n// instead of reopening, and stealing focus, when the rail comes back.\nwatch(railOn, () => { if (!config.value.groups && selection.value.length) setSelection([]) })\n\nfunction handleSelection(wellIds: string[]) {\n if (selectionMode.value === 'none' || dragMode.value) return\n setSelection(wellIds)\n}\n\nfunction handleWellEdit(wellId: string, data: WellEditData) {\n const rack = activeRack.value\n if (!rack) return\n const fields = config.value.wellFields || []\n // An unnamed sample is an empty well, unless its group still names it; blanks and QCs stand alone.\n const grouped = groupList.value.some(group => group.id === rack.wells[wellId]?.group)\n const sample = !data.sampleType || data.sampleType === 'sample'\n if (fields.includes('label') && !data.label && sample && !grouped) emit('well-clear', rack.id, wellId)\n else emit('well-edit', rack.id, wellId, data)\n}\n\nconst railWellId = computed(() =>\n railOn.value && inspectorOn.value && selection.value.length === 1 ? selection.value[0] : undefined,\n)\nconst railGroupLabel = computed(() => {\n const groupId = railWellId.value && activeRack.value?.wells[railWellId.value]?.group\n return groupList.value.find(group => group.id === groupId)?.name\n})\nconst railPosition = () => {\n const rect = railRef.value?.getBoundingClientRect()\n return { x: rect?.left ?? 0, y: rect?.top ?? 0 }\n}\n\n// Like the popover's Clear, the rail's closes the inspector and hands focus back to the well.\nfunction clearRailWell() {\n emit('well-clear', activeRack.value!.id, railWellId.value!)\n setSelection([])\n}\n\nfunction assign(groupId: string | undefined) {\n if (!activeRack.value) return\n emit('assign', activeRack.value.id, selection.value, groupId)\n setSelection([])\n}\n\nconst hint = computed(() => {\n if (dragMode.value) return 'Drag a filled well onto another to move or swap it.'\n if (props.hint) return props.hint\n if (!config.value.groups) return ''\n if (railOn.value) {\n return inspectorOn.value\n ? 'Click a well to edit it on the right. Drag across wells, Ctrl-click to add one or Shift-click for a range.'\n : 'Click or drag across wells to select them.'\n }\n return inspectorOn.value\n ? 'Click a well to edit it. Ctrl- or Cmd-click to select several, Shift-click for a range.'\n : 'Click or drag across wells to select them.'\n})\n</script>\n\n<template>\n <div\n ref=\"rootRef\"\n :class=\"['mint-plate-editor', { 'mint-plate-editor--readonly': readonly, 'mint-plate-editor--rail': railOn }]\"\n tabindex=\"-1\"\n @keydown=\"handleRootKeydown\"\n >\n <!-- Plate tabs -->\n <div class=\"mint-plate-editor__tabbar\">\n <div class=\"mint-plate-editor__tabs\" role=\"tablist\" aria-label=\"Plates\">\n <!-- Tab and close button stay siblings; the item carries the shared underline. -->\n <span\n v-for=\"(rack, index) in modelValue\"\n :key=\"rack.id\"\n role=\"presentation\"\n :class=\"['mint-plate-editor__tab-item', { 'mint-plate-editor__tab-item--active': rack.id === activeRack?.id }]\"\n >\n <button\n :ref=\"(el) => setTabRef(rack.id, el)\"\n type=\"button\"\n role=\"tab\"\n :aria-selected=\"rack.id === activeRack?.id\"\n :tabindex=\"rack.id === activeRack?.id ? 0 : -1\"\n :draggable=\"canReorder\"\n :title=\"canReorder ? `${rack.name} — Alt with ← / → reorders` : undefined\"\n :class=\"[\n 'mint-plate-editor__tab',\n {\n 'mint-plate-editor__tab--dragging': reorder.draggingId.value === rack.id,\n 'mint-plate-editor__tab--drag-over': reorder.dragOverId.value === rack.id,\n },\n ]\"\n @click=\"selectRack(rack.id)\"\n @keydown=\"handleTabKeydown($event, rack, index)\"\n @dragstart=\"reorder.onDragStart($event, rack, index)\"\n @dragover=\"reorder.onDragOver($event, rack, index)\"\n @dragleave=\"reorder.onDragLeave\"\n @drop=\"reorder.onDrop($event, rack, index)\"\n @dragend=\"reorder.onDragEnd\"\n >\n <span :class=\"['mint-plate-editor__slot-dot', `mint-plate-editor__slot-dot--${rack.slot.toLowerCase()}`]\" aria-hidden=\"true\" />\n {{ rack.name }}\n <span class=\"mint-plate-editor__tab-count\">{{ filledCount(rack) }}</span>\n </button>\n <button\n v-if=\"rack.id === activeRack?.id && canRemove\"\n type=\"button\"\n class=\"mint-plate-editor__tab-close\"\n :aria-label=\"`Remove ${rack.name}`\"\n :title=\"`Remove ${rack.name}`\"\n @click=\"emit('rack-remove', rack.id)\"\n >\n <X :size=\"12\" aria-hidden=\"true\" />\n </button>\n </span>\n </div>\n\n <BaseButton\n v-if=\"canAdd\"\n class=\"mint-plate-editor__add\"\n variant=\"ghost\"\n size=\"sm\"\n :icon=\"Plus\"\n label=\"Add plate\"\n @click=\"emit('rack-add')\"\n />\n\n <div class=\"mint-plate-editor__tabbar-end\">\n <template v-if=\"showHistory\">\n <BaseButton variant=\"ghost\" size=\"sm\" :icon=\"Undo2\" label=\"Undo\" title=\"Undo (Ctrl+Z)\" data-history=\"undo\" :disabled=\"!canUndo\" @click=\"emit('undo')\" />\n <BaseButton variant=\"ghost\" size=\"sm\" :icon=\"Redo2\" label=\"Redo\" title=\"Redo (Ctrl+Shift+Z)\" data-history=\"redo\" :disabled=\"!canRedo\" @click=\"emit('redo')\" />\n </template>\n <template v-if=\"!readonly && (config.import || exportFormats.length)\">\n <span v-if=\"showHistory\" class=\"mint-plate-editor__divider\" aria-hidden=\"true\" />\n <ActionMenu v-if=\"exportFormats.length || ioItems.length > 1\" :label=\"ioLabel\" :items=\"ioItems\" @select=\"handleIo\">\n <template #trigger=\"{ open, toggle, keydown }\">\n <button\n type=\"button\"\n class=\"mint-plate-editor__action\"\n data-action=\"import-export\"\n aria-haspopup=\"menu\"\n :aria-expanded=\"open\"\n @click=\"toggle\"\n @keydown=\"keydown\"\n ><component :is=\"exportFormats.length ? Download : Upload\" :size=\"14\" aria-hidden=\"true\" />{{ ioLabel }}</button>\n </template>\n </ActionMenu>\n <button v-else type=\"button\" class=\"mint-plate-editor__action\" @click=\"handleIo(ioItems[0])\">\n <Upload :size=\"14\" aria-hidden=\"true\" />\n Import\n </button>\n <input v-if=\"importFile\" ref=\"fileInput\" type=\"file\" class=\"mint-plate-editor__file\" :accept=\"config.importAccept\" tabindex=\"-1\" aria-hidden=\"true\" @change=\"handleFile\">\n </template>\n <slot name=\"tab-extra\" />\n </div>\n </div>\n\n <!-- Toolbar for the active plate -->\n <div v-if=\"showToolbar && activeRack\" class=\"mint-plate-editor__toolbar\">\n <div v-if=\"config.formats.length > 1\" class=\"mint-plate-editor__field mint-plate-editor__field--format\">\n <span class=\"mint-plate-editor__label\">Format</span>\n <SegmentedControl\n :model-value=\"activeRack.format\"\n :options=\"formatOptions\"\n size=\"sm\"\n aria-label=\"Format\"\n @update:model-value=\"emit('update:format', activeRack.id, Number($event) as WellPlateFormat)\"\n />\n </div>\n\n <div v-if=\"slotOptions.length\" class=\"mint-plate-editor__field\">\n <span class=\"mint-plate-editor__label\">Slot</span>\n <div class=\"mint-plate-editor__slots\" role=\"group\" aria-label=\"Slot\">\n <button\n v-for=\"slot in slotOptions\"\n :key=\"slot\"\n type=\"button\"\n :aria-pressed=\"activeRack.slot === slot\"\n :class=\"[\n 'mint-plate-editor__slot',\n `mint-plate-editor__slot--${slot.toLowerCase()}`,\n { 'mint-plate-editor__slot--active': activeRack.slot === slot },\n ]\"\n @click=\"emit('update:slot', activeRack.id, slot)\"\n >\n {{ slot }}\n </button>\n </div>\n </div>\n\n <div class=\"mint-plate-editor__toolbar-end\">\n <button\n v-if=\"config.fillSeries\"\n type=\"button\"\n class=\"mint-plate-editor__action\"\n data-action=\"fill-series\"\n :disabled=\"isFull && config.fillSeriesWhenFull === 'disable'\"\n :title=\"isFull ? 'Plate is full' : 'Name every empty well in order'\"\n @click=\"emit('fill-series', activeRack.id)\"\n >\n Fill Series\n </button>\n <button\n v-if=\"config.wellDrag\"\n type=\"button\"\n :class=\"['mint-plate-editor__action', { 'mint-plate-editor__action--on': dragMode }]\"\n data-action=\"drag\"\n :aria-pressed=\"dragMode\"\n title=\"Drag wells to move or swap them\"\n @click=\"toggleDrag\"\n >\n <Move :size=\"14\" aria-hidden=\"true\" />\n Drag\n </button>\n <button\n v-if=\"config.clear\"\n type=\"button\"\n class=\"mint-plate-editor__action mint-plate-editor__action--danger\"\n :disabled=\"isEmpty\"\n title=\"Clear every well on this plate\"\n @click=\"requestClear\"\n >\n <Trash2 :size=\"14\" aria-hidden=\"true\" />\n Clear\n </button>\n <slot name=\"toolbar-extra\" />\n </div>\n </div>\n\n <!-- Plate -->\n <div v-if=\"activeRack\" class=\"mint-plate-editor__body\">\n <div class=\"mint-plate-editor__main\">\n <!-- Keyed by rack: the Shift-click anchor and an open popover belong to one plate. -->\n <WellPlate\n :key=\"activeRack.id\"\n :model-value=\"selection\"\n :spec=\"plateSpec\"\n :wells=\"activeRack.wells\"\n :readonly=\"readonly\"\n @well-click=\"(wellId: string) => emit('well-click', activeRack!.id, wellId)\"\n @selection-change=\"handleSelection\"\n @well-edit=\"handleWellEdit\"\n @well-clear=\"(wellId: string) => emit('well-clear', activeRack!.id, wellId)\"\n @well-move=\"(source: string, target: string) => emit('well-move', activeRack!.id, source, target)\"\n @sample-drop=\"(wellId: string, data: WellSampleDropData, event: DragEvent) => emit('sample-drop', activeRack!.id, wellId, data, event)\"\n >\n <template v-if=\"$slots['well-editor']\" #well-editor=\"slotProps\">\n <slot name=\"well-editor\" v-bind=\"slotProps\" :rack=\"activeRack\" :rack-id=\"activeRack.id\" :inline=\"false\" />\n </template>\n </WellPlate>\n\n <!-- Selection bar, or a hint while nothing is selected -->\n <div v-if=\"!readonly && (config.groups || config.wellDrag)\" class=\"mint-plate-editor__footer\">\n <div v-if=\"selection.length && config.groups\" class=\"mint-plate-editor__selection\" role=\"toolbar\" aria-label=\"Selected wells\">\n <span class=\"mint-plate-editor__selection-count\">\n <span class=\"mint-plate-editor__mono\">{{ selection.length }}</span> selected\n </span>\n <span class=\"mint-plate-editor__divider\" aria-hidden=\"true\" />\n <span class=\"mint-plate-editor__label\">Group</span>\n <button\n v-for=\"(group, index) in config.groups\"\n :key=\"group.id\"\n type=\"button\"\n class=\"mint-plate-editor__group\"\n @click=\"assign(group.id)\"\n >\n <span class=\"mint-plate-editor__group-swatch\" :style=\"{ backgroundColor: groupColor(group, index) }\" aria-hidden=\"true\" />\n {{ group.name }}\n </button>\n <span class=\"mint-plate-editor__divider\" aria-hidden=\"true\" />\n <button\n type=\"button\"\n class=\"mint-plate-editor__icon-action mint-plate-editor__icon-action--danger\"\n aria-label=\"Clear selected wells\"\n title=\"Clear selected wells\"\n @click=\"assign(undefined)\"\n >\n <Trash2 :size=\"14\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n class=\"mint-plate-editor__icon-action\"\n aria-label=\"Cancel selection\"\n title=\"Cancel selection (Esc)\"\n @click=\"setSelection([])\"\n >\n <X :size=\"14\" aria-hidden=\"true\" />\n </button>\n </div>\n <span v-else-if=\"hint\" class=\"mint-plate-editor__hint\">{{ hint }}</span>\n </div>\n </div>\n\n <aside v-if=\"railOn\" ref=\"railRef\" class=\"mint-plate-editor__rail\" aria-label=\"Plate details\">\n <template v-if=\"railWellId\">\n <slot\n name=\"well-editor\"\n :rack=\"activeRack\"\n :rack-id=\"activeRack.id\"\n :well-id=\"railWellId\"\n :well-data=\"activeRack.wells[railWellId]\"\n :edit-fields=\"config.wellFields || []\"\n :default-injection-volume=\"activeRack.injectionVolume\"\n :position=\"railPosition()\"\n :save=\"(data: WellEditData) => handleWellEdit(railWellId!, data)\"\n :clear=\"clearRailWell\"\n :close=\"() => setSelection([])\"\n :inline=\"true\"\n >\n <WellEditPopupInternal\n inline\n :well-id=\"railWellId\"\n :well-data=\"activeRack.wells[railWellId]\"\n :edit-fields=\"config.wellFields || undefined\"\n :default-injection-volume=\"activeRack.injectionVolume\"\n :marker-colors=\"sampleColors\"\n :group-label=\"railGroupLabel\"\n :position=\"{ x: 0, y: 0 }\"\n @save=\"handleWellEdit(railWellId!, $event)\"\n @clear=\"clearRailWell\"\n @close=\"setSelection([])\"\n />\n </slot>\n </template>\n <template v-else>\n <span class=\"mint-plate-editor__rail-title\">{{ config.groups ? 'Groups' : 'Sample types' }}</span>\n <ul class=\"mint-plate-editor__rail-list\">\n <li v-for=\"item in railItems\" :key=\"item.type\" class=\"mint-plate-editor__rail-row\">\n <span\n :class=\"['mint-plate-editor__rail-swatch', { 'mint-plate-editor__rail-swatch--marker': !config.groups }]\"\n :style=\"item.swatch\"\n aria-hidden=\"true\"\n >{{ item.glyph }}</span>\n <span class=\"mint-plate-editor__rail-name\">{{ item.label }}</span>\n <span class=\"mint-plate-editor__rail-count\">{{ item.count }}</span>\n </li>\n </ul>\n <button\n v-if=\"config.groups && !readonly\"\n type=\"button\"\n class=\"mint-plate-editor__rail-add\"\n data-action=\"group-add\"\n @click=\"emit('group-add')\"\n >\n <Plus :size=\"12\" aria-hidden=\"true\" />\n New group\n </button>\n </template>\n </aside>\n </div>\n\n <ConfirmDialog\n v-if=\"config.clear === 'confirm'\"\n v-model:open=\"confirmOpen\"\n :title=\"config.clearConfirm?.title ?? 'Clear plate'\"\n :message=\"config.clearConfirm?.message ?? `Clear all ${activeRack ? filledCount(activeRack) : 0} wells on ${activeRack?.name ?? 'this plate'}?`\"\n :confirm-label=\"config.clearConfirm?.confirmLabel ?? 'Clear'\"\n :tone=\"config.clearConfirm?.tone\"\n @confirm=\"confirmClear\"\n />\n\n <ConfirmDialog\n v-if=\"importPaste\"\n v-model:open=\"pasteOpen\"\n title=\"Paste wells\"\n message=\"Paste the rows to import, e.g. copied from a spreadsheet.\"\n confirm-label=\"Import\"\n tone=\"info\"\n @confirm=\"confirmPaste\"\n >\n <BaseInput type=\"textarea\" v-model=\"pasteText\" :rows=\"8\" aria-label=\"Pasted rows\" />\n </ConfirmDialog>\n\n <div role=\"status\" aria-live=\"polite\" :style=\"reorder.announcerStyle\">{{ reorder.announcement.value }}</div>\n </div>\n</template>\n","import {\n useConcentrationUnits,\n type ConcentrationValue,\n type MolarityUnit,\n type MassVolumeUnit,\n} from '../composables/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\n/** Volume units, smallest first. */\nexport const volumeUnits: VolumeUnit[] = ['µL', 'mL', 'L']\n\nconst VOLUME_FACTORS: Record<VolumeUnit, number> = {\n 'µL': 1e-6,\n 'mL': 1e-3,\n 'L': 1,\n}\n\nconst { convert } = useConcentrationUnits()\n\nexport 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\nexport 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\nexport 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\nexport 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\nexport 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\nexport 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\nfunction 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\nfunction 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\nexport 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","<script setup lang=\"ts\">\n/** Volume field for DoseCalculator: a FormField row around a NumberInput with the volume units. */\nimport { computed } from 'vue'\nimport type { VolumeUnit, VolumeValue } from '../utils/dose'\nimport FormField from './FormField.vue'\nimport NumberInput from './NumberInput.vue'\n\ninterface Props {\n label: string\n modelValue: VolumeValue\n units: VolumeUnit[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: VolumeValue]\n}>()\n\nconst unitOptions = computed(() => props.units.map((unit) => ({ value: unit, label: unit })))\n\nfunction handleValue(value: number | null) {\n if (value === null) return\n emit('update:modelValue', { ...props.modelValue, value })\n}\n\nfunction handleUnit(unit: string) {\n emit('update:modelValue', { ...props.modelValue, unit: unit as VolumeUnit })\n}\n</script>\n\n<template>\n <FormField :label=\"label\" layout=\"row\">\n <NumberInput\n size=\"sm\"\n :model-value=\"modelValue.value\"\n :unit=\"modelValue.unit\"\n :units=\"unitOptions\"\n :min=\"0\"\n :disabled=\"disabled\"\n @update:model-value=\"handleValue\"\n @update:unit=\"handleUnit\"\n />\n </FormField>\n</template>\n","<script setup lang=\"ts\">\n/** Volume field for DoseCalculator: a FormField row around a NumberInput with the volume units. */\nimport { computed } from 'vue'\nimport type { VolumeUnit, VolumeValue } from '../utils/dose'\nimport FormField from './FormField.vue'\nimport NumberInput from './NumberInput.vue'\n\ninterface Props {\n label: string\n modelValue: VolumeValue\n units: VolumeUnit[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: VolumeValue]\n}>()\n\nconst unitOptions = computed(() => props.units.map((unit) => ({ value: unit, label: unit })))\n\nfunction handleValue(value: number | null) {\n if (value === null) return\n emit('update:modelValue', { ...props.modelValue, value })\n}\n\nfunction handleUnit(unit: string) {\n emit('update:modelValue', { ...props.modelValue, unit: unit as VolumeUnit })\n}\n</script>\n\n<template>\n <FormField :label=\"label\" layout=\"row\">\n <NumberInput\n size=\"sm\"\n :model-value=\"modelValue.value\"\n :unit=\"modelValue.unit\"\n :units=\"unitOptions\"\n :min=\"0\"\n :disabled=\"disabled\"\n @update:model-value=\"handleValue\"\n @update:unit=\"handleUnit\"\n />\n </FormField>\n</template>\n","<script setup lang=\"ts\">\n/** Tabbed dosing calculator supporting single dilution, serial dilution, and unit conversion, with optional direct assignment to selected plate wells. */\nimport { ref, computed, watch } from 'vue'\nimport BaseButton from './BaseButton.vue'\nimport NumberInput from './NumberInput.vue'\nimport ConcentrationInput from './ConcentrationInput.vue'\nimport FormField from './FormField.vue'\nimport {\n useConcentrationUnits,\n type ConcentrationValue,\n} from '../composables/useConcentrationUnits'\nimport {\n calculateDilution,\n calculateSerialDilution,\n formatVolume,\n generateWellConcentrations,\n volumeUnits,\n type VolumeValue,\n type DilutionResult,\n type SerialDilutionResult,\n type WellConcentration,\n} from '../utils/dose'\nimport DoseCalculatorVolumeField from './DoseCalculatorVolumeField.vue'\n\ntype CalculatorMode = 'dilution' | 'serial' | 'conversion'\n\ninterface Props {\n mode?: CalculatorMode | 'auto'\n molecularWeight?: number\n targetWells?: string[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'auto',\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'apply-to-wells': [results: WellConcentration[]]\n 'calculate': [result: DilutionResult | SerialDilutionResult]\n}>()\n\nconst { formatWithUnit } = useConcentrationUnits()\n\n// Active mode (for tabbed interface)\nconst activeMode = ref<CalculatorMode>(props.mode === 'auto' ? 'dilution' : props.mode)\n\n// Dilution form state\nconst stockConcentration = ref<ConcentrationValue>({ value: 10, unit: 'mM' })\nconst finalConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst finalVolume = ref<VolumeValue>({ value: 1, unit: 'mL' })\n\n// Serial dilution form state\nconst serialStartConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst serialDilutionFactor = ref(3)\nconst serialNumberOfDilutions = ref(8)\nconst serialVolumePerWell = ref<VolumeValue>({ value: 100, unit: 'µL' })\n\n// Results\nconst dilutionResult = ref<DilutionResult | null>(null)\nconst serialResult = ref<SerialDilutionResult | null>(null)\n\n// Computed\nconst showTabs = computed(() => props.mode === 'auto')\n\nconst hasTargetWells = computed(() => {\n return props.targetWells && props.targetWells.length > 0\n})\n\nconst canApplyToWells = computed(() => {\n if (!hasTargetWells.value) return false\n if (activeMode.value === 'serial' && serialResult.value?.valid) return true\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) return true\n return false\n})\n\n// Calculate dilution result\nfunction recalculateDilution() {\n if (!stockConcentration.value || !finalConcentration.value || !finalVolume.value) {\n dilutionResult.value = null\n return\n }\n\n dilutionResult.value = calculateDilution({\n stockConcentration: stockConcentration.value,\n finalConcentration: finalConcentration.value,\n finalVolume: finalVolume.value,\n })\n\n if (dilutionResult.value.valid) {\n emit('calculate', dilutionResult.value)\n }\n}\n\n// Calculate serial dilution result\nfunction recalculateSerial() {\n if (\n !serialStartConcentration.value ||\n serialDilutionFactor.value <= 1 ||\n serialNumberOfDilutions.value < 1 ||\n !serialVolumePerWell.value\n ) {\n serialResult.value = null\n return\n }\n\n serialResult.value = calculateSerialDilution({\n startingConcentration: serialStartConcentration.value,\n dilutionFactor: serialDilutionFactor.value,\n numberOfDilutions: serialNumberOfDilutions.value,\n volumePerWell: serialVolumePerWell.value,\n })\n\n if (serialResult.value.valid) {\n emit('calculate', serialResult.value)\n }\n}\n\n// Apply to wells\nfunction handleApplyToWells() {\n if (!props.targetWells || props.targetWells.length === 0) return\n\n if (activeMode.value === 'serial' && serialResult.value?.valid) {\n const wellConcentrations = generateWellConcentrations(\n serialResult.value,\n props.targetWells\n )\n emit('apply-to-wells', wellConcentrations)\n }\n\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) {\n // For simple dilution, apply same concentration to all wells\n const wellConcentrations: WellConcentration[] = props.targetWells.map(wellId => ({\n wellId,\n concentration: finalConcentration.value,\n volume: finalVolume.value,\n }))\n emit('apply-to-wells', wellConcentrations)\n }\n}\n\n// Watch for changes and recalculate\nwatch(\n [stockConcentration, finalConcentration, finalVolume],\n () => {\n if (activeMode.value === 'dilution') {\n recalculateDilution()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(\n [serialStartConcentration, serialDilutionFactor, serialNumberOfDilutions, serialVolumePerWell],\n () => {\n if (activeMode.value === 'serial') {\n recalculateSerial()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(activeMode, (mode) => {\n if (mode === 'dilution') {\n recalculateDilution()\n } else if (mode === 'serial') {\n recalculateSerial()\n }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dose-calculator',\n disabled ? 'mint-dose-calculator--disabled' : '',\n ]\"\n >\n <!-- Tabs -->\n <div v-if=\"showTabs\" class=\"mint-dose-calculator__tabs\" role=\"tablist\">\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'dilution' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'dilution'\"\n @click=\"activeMode = 'dilution'\"\n >\n Dilution\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'serial' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'serial'\"\n @click=\"activeMode = 'serial'\"\n >\n Serial\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'conversion' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'conversion'\"\n @click=\"activeMode = 'conversion'\"\n >\n Conversion\n </button>\n </div>\n\n <!-- Dilution form -->\n <div\n v-if=\"activeMode === 'dilution'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <FormField label=\"Stock Concentration\">\n <ConcentrationInput\n v-model=\"stockConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"sm\"\n />\n </FormField>\n\n <FormField label=\"Final Concentration\">\n <ConcentrationInput\n v-model=\"finalConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"sm\"\n />\n </FormField>\n\n <DoseCalculatorVolumeField\n v-model=\"finalVolume\"\n label=\"Final Volume\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Dilution Result -->\n <div v-if=\"dilutionResult?.valid\" class=\"mint-dose-calculator__result\">\n <div class=\"mint-dose-calculator__result-title\">Result</div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Stock volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.stockVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Diluent volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.diluentVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Dilution factor</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ dilutionResult.dilutionFactor.toFixed(1) }}×\n </span>\n </div>\n </div>\n\n <div v-else-if=\"dilutionResult?.error\" class=\"mint-dose-calculator__error\">\n {{ dilutionResult.error }}\n </div>\n </div>\n\n <!-- Serial Dilution form -->\n <div\n v-else-if=\"activeMode === 'serial'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <FormField label=\"Starting Concentration\">\n <ConcentrationInput\n v-model=\"serialStartConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"sm\"\n />\n </FormField>\n\n <FormField label=\"Dilution Factor\" layout=\"row\">\n <NumberInput\n size=\"sm\"\n :model-value=\"serialDilutionFactor\"\n :disabled=\"disabled\"\n :min=\"1.1\"\n :step=\"0.1\"\n unit=\"×\"\n @update:model-value=\"serialDilutionFactor = $event ?? 0\"\n />\n </FormField>\n\n <FormField label=\"Dilutions\" layout=\"row\">\n <NumberInput\n size=\"sm\"\n :model-value=\"serialNumberOfDilutions\"\n :disabled=\"disabled\"\n :min=\"1\"\n :max=\"24\"\n @update:model-value=\"serialNumberOfDilutions = $event ?? 0\"\n />\n </FormField>\n\n <DoseCalculatorVolumeField\n v-model=\"serialVolumePerWell\"\n label=\"Volume per Well\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Serial Preview -->\n <div v-if=\"serialResult?.valid\" class=\"mint-dose-calculator__preview\">\n <div class=\"mint-dose-calculator__preview-title\">Preview</div>\n <div class=\"mint-dose-calculator__preview-steps\">\n <template v-for=\"(step, index) in serialResult.steps\" :key=\"step.stepNumber\">\n <div class=\"mint-dose-calculator__preview-step\">\n <span class=\"mint-dose-calculator__preview-step-num\">{{ step.stepNumber }}:</span>\n <span class=\"mint-dose-calculator__preview-step-conc\">\n {{ formatWithUnit(step.concentration, 3) }}\n </span>\n </div>\n <span\n v-if=\"index < serialResult.steps.length - 1\"\n class=\"mint-dose-calculator__preview-arrow\"\n >\n →\n </span>\n </template>\n </div>\n </div>\n\n <div v-else-if=\"serialResult?.error\" class=\"mint-dose-calculator__error\">\n {{ serialResult.error }}\n </div>\n </div>\n\n <!-- Conversion form -->\n <div\n v-else-if=\"activeMode === 'conversion'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <FormField label=\"Input Concentration\">\n <ConcentrationInput\n :disabled=\"disabled\"\n :molecular-weight=\"molecularWeight\"\n :show-conversion=\"true\"\n size=\"sm\"\n />\n </FormField>\n\n <div\n v-if=\"!molecularWeight\"\n class=\"mint-dose-calculator__error\"\n >\n Provide molecular weight for mass ↔ molarity conversion\n </div>\n </div>\n\n <!-- Molecular weight section (shown if needed) -->\n <div\n v-if=\"molecularWeight\"\n class=\"mint-dose-calculator__mw-section\"\n >\n <span class=\"mint-dose-calculator__mw-label\">\n Molecular Weight: {{ molecularWeight }} g/mol\n </span>\n </div>\n\n <!-- Apply to wells button -->\n <div v-if=\"hasTargetWells\" class=\"mint-dose-calculator__form\">\n <BaseButton\n variant=\"primary\"\n full-width\n :disabled=\"!canApplyToWells || disabled\"\n @click=\"handleApplyToWells\"\n >\n Apply to {{ targetWells?.length }} selected well{{ targetWells?.length === 1 ? '' : 's' }}\n </BaseButton>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Tabbed dosing calculator supporting single dilution, serial dilution, and unit conversion, with optional direct assignment to selected plate wells. */\nimport { ref, computed, watch } from 'vue'\nimport BaseButton from './BaseButton.vue'\nimport NumberInput from './NumberInput.vue'\nimport ConcentrationInput from './ConcentrationInput.vue'\nimport FormField from './FormField.vue'\nimport {\n useConcentrationUnits,\n type ConcentrationValue,\n} from '../composables/useConcentrationUnits'\nimport {\n calculateDilution,\n calculateSerialDilution,\n formatVolume,\n generateWellConcentrations,\n volumeUnits,\n type VolumeValue,\n type DilutionResult,\n type SerialDilutionResult,\n type WellConcentration,\n} from '../utils/dose'\nimport DoseCalculatorVolumeField from './DoseCalculatorVolumeField.vue'\n\ntype CalculatorMode = 'dilution' | 'serial' | 'conversion'\n\ninterface Props {\n mode?: CalculatorMode | 'auto'\n molecularWeight?: number\n targetWells?: string[]\n disabled?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n mode: 'auto',\n disabled: false,\n})\n\nconst emit = defineEmits<{\n 'apply-to-wells': [results: WellConcentration[]]\n 'calculate': [result: DilutionResult | SerialDilutionResult]\n}>()\n\nconst { formatWithUnit } = useConcentrationUnits()\n\n// Active mode (for tabbed interface)\nconst activeMode = ref<CalculatorMode>(props.mode === 'auto' ? 'dilution' : props.mode)\n\n// Dilution form state\nconst stockConcentration = ref<ConcentrationValue>({ value: 10, unit: 'mM' })\nconst finalConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst finalVolume = ref<VolumeValue>({ value: 1, unit: 'mL' })\n\n// Serial dilution form state\nconst serialStartConcentration = ref<ConcentrationValue>({ value: 100, unit: 'µM' })\nconst serialDilutionFactor = ref(3)\nconst serialNumberOfDilutions = ref(8)\nconst serialVolumePerWell = ref<VolumeValue>({ value: 100, unit: 'µL' })\n\n// Results\nconst dilutionResult = ref<DilutionResult | null>(null)\nconst serialResult = ref<SerialDilutionResult | null>(null)\n\n// Computed\nconst showTabs = computed(() => props.mode === 'auto')\n\nconst hasTargetWells = computed(() => {\n return props.targetWells && props.targetWells.length > 0\n})\n\nconst canApplyToWells = computed(() => {\n if (!hasTargetWells.value) return false\n if (activeMode.value === 'serial' && serialResult.value?.valid) return true\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) return true\n return false\n})\n\n// Calculate dilution result\nfunction recalculateDilution() {\n if (!stockConcentration.value || !finalConcentration.value || !finalVolume.value) {\n dilutionResult.value = null\n return\n }\n\n dilutionResult.value = calculateDilution({\n stockConcentration: stockConcentration.value,\n finalConcentration: finalConcentration.value,\n finalVolume: finalVolume.value,\n })\n\n if (dilutionResult.value.valid) {\n emit('calculate', dilutionResult.value)\n }\n}\n\n// Calculate serial dilution result\nfunction recalculateSerial() {\n if (\n !serialStartConcentration.value ||\n serialDilutionFactor.value <= 1 ||\n serialNumberOfDilutions.value < 1 ||\n !serialVolumePerWell.value\n ) {\n serialResult.value = null\n return\n }\n\n serialResult.value = calculateSerialDilution({\n startingConcentration: serialStartConcentration.value,\n dilutionFactor: serialDilutionFactor.value,\n numberOfDilutions: serialNumberOfDilutions.value,\n volumePerWell: serialVolumePerWell.value,\n })\n\n if (serialResult.value.valid) {\n emit('calculate', serialResult.value)\n }\n}\n\n// Apply to wells\nfunction handleApplyToWells() {\n if (!props.targetWells || props.targetWells.length === 0) return\n\n if (activeMode.value === 'serial' && serialResult.value?.valid) {\n const wellConcentrations = generateWellConcentrations(\n serialResult.value,\n props.targetWells\n )\n emit('apply-to-wells', wellConcentrations)\n }\n\n if (activeMode.value === 'dilution' && dilutionResult.value?.valid) {\n // For simple dilution, apply same concentration to all wells\n const wellConcentrations: WellConcentration[] = props.targetWells.map(wellId => ({\n wellId,\n concentration: finalConcentration.value,\n volume: finalVolume.value,\n }))\n emit('apply-to-wells', wellConcentrations)\n }\n}\n\n// Watch for changes and recalculate\nwatch(\n [stockConcentration, finalConcentration, finalVolume],\n () => {\n if (activeMode.value === 'dilution') {\n recalculateDilution()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(\n [serialStartConcentration, serialDilutionFactor, serialNumberOfDilutions, serialVolumePerWell],\n () => {\n if (activeMode.value === 'serial') {\n recalculateSerial()\n }\n },\n { deep: true, immediate: true }\n)\n\nwatch(activeMode, (mode) => {\n if (mode === 'dilution') {\n recalculateDilution()\n } else if (mode === 'serial') {\n recalculateSerial()\n }\n})\n</script>\n\n<template>\n <div\n :class=\"[\n 'mint-dose-calculator',\n disabled ? 'mint-dose-calculator--disabled' : '',\n ]\"\n >\n <!-- Tabs -->\n <div v-if=\"showTabs\" class=\"mint-dose-calculator__tabs\" role=\"tablist\">\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'dilution' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'dilution'\"\n @click=\"activeMode = 'dilution'\"\n >\n Dilution\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'serial' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'serial'\"\n @click=\"activeMode = 'serial'\"\n >\n Serial\n </button>\n <button\n type=\"button\"\n :class=\"[\n 'mint-dose-calculator__tab',\n activeMode === 'conversion' ? 'mint-dose-calculator__tab--active' : '',\n ]\"\n role=\"tab\"\n :aria-selected=\"activeMode === 'conversion'\"\n @click=\"activeMode = 'conversion'\"\n >\n Conversion\n </button>\n </div>\n\n <!-- Dilution form -->\n <div\n v-if=\"activeMode === 'dilution'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <FormField label=\"Stock Concentration\">\n <ConcentrationInput\n v-model=\"stockConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"sm\"\n />\n </FormField>\n\n <FormField label=\"Final Concentration\">\n <ConcentrationInput\n v-model=\"finalConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"sm\"\n />\n </FormField>\n\n <DoseCalculatorVolumeField\n v-model=\"finalVolume\"\n label=\"Final Volume\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Dilution Result -->\n <div v-if=\"dilutionResult?.valid\" class=\"mint-dose-calculator__result\">\n <div class=\"mint-dose-calculator__result-title\">Result</div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Stock volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.stockVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Diluent volume</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ formatVolume(dilutionResult.diluentVolume) }}\n </span>\n </div>\n <div class=\"mint-dose-calculator__result-row\">\n <span class=\"mint-dose-calculator__result-label\">Dilution factor</span>\n <span class=\"mint-dose-calculator__result-value\">\n {{ dilutionResult.dilutionFactor.toFixed(1) }}×\n </span>\n </div>\n </div>\n\n <div v-else-if=\"dilutionResult?.error\" class=\"mint-dose-calculator__error\">\n {{ dilutionResult.error }}\n </div>\n </div>\n\n <!-- Serial Dilution form -->\n <div\n v-else-if=\"activeMode === 'serial'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <FormField label=\"Starting Concentration\">\n <ConcentrationInput\n v-model=\"serialStartConcentration\"\n :disabled=\"disabled\"\n :show-conversion=\"false\"\n size=\"sm\"\n />\n </FormField>\n\n <FormField label=\"Dilution Factor\" layout=\"row\">\n <NumberInput\n size=\"sm\"\n :model-value=\"serialDilutionFactor\"\n :disabled=\"disabled\"\n :min=\"1.1\"\n :step=\"0.1\"\n unit=\"×\"\n @update:model-value=\"serialDilutionFactor = $event ?? 0\"\n />\n </FormField>\n\n <FormField label=\"Dilutions\" layout=\"row\">\n <NumberInput\n size=\"sm\"\n :model-value=\"serialNumberOfDilutions\"\n :disabled=\"disabled\"\n :min=\"1\"\n :max=\"24\"\n @update:model-value=\"serialNumberOfDilutions = $event ?? 0\"\n />\n </FormField>\n\n <DoseCalculatorVolumeField\n v-model=\"serialVolumePerWell\"\n label=\"Volume per Well\"\n :units=\"volumeUnits\"\n :disabled=\"disabled\"\n />\n\n <div class=\"mint-dose-calculator__divider\" />\n\n <!-- Serial Preview -->\n <div v-if=\"serialResult?.valid\" class=\"mint-dose-calculator__preview\">\n <div class=\"mint-dose-calculator__preview-title\">Preview</div>\n <div class=\"mint-dose-calculator__preview-steps\">\n <template v-for=\"(step, index) in serialResult.steps\" :key=\"step.stepNumber\">\n <div class=\"mint-dose-calculator__preview-step\">\n <span class=\"mint-dose-calculator__preview-step-num\">{{ step.stepNumber }}:</span>\n <span class=\"mint-dose-calculator__preview-step-conc\">\n {{ formatWithUnit(step.concentration, 3) }}\n </span>\n </div>\n <span\n v-if=\"index < serialResult.steps.length - 1\"\n class=\"mint-dose-calculator__preview-arrow\"\n >\n →\n </span>\n </template>\n </div>\n </div>\n\n <div v-else-if=\"serialResult?.error\" class=\"mint-dose-calculator__error\">\n {{ serialResult.error }}\n </div>\n </div>\n\n <!-- Conversion form -->\n <div\n v-else-if=\"activeMode === 'conversion'\"\n class=\"mint-dose-calculator__form\"\n role=\"tabpanel\"\n >\n <FormField label=\"Input Concentration\">\n <ConcentrationInput\n :disabled=\"disabled\"\n :molecular-weight=\"molecularWeight\"\n :show-conversion=\"true\"\n size=\"sm\"\n />\n </FormField>\n\n <div\n v-if=\"!molecularWeight\"\n class=\"mint-dose-calculator__error\"\n >\n Provide molecular weight for mass ↔ molarity conversion\n </div>\n </div>\n\n <!-- Molecular weight section (shown if needed) -->\n <div\n v-if=\"molecularWeight\"\n class=\"mint-dose-calculator__mw-section\"\n >\n <span class=\"mint-dose-calculator__mw-label\">\n Molecular Weight: {{ molecularWeight }} g/mol\n </span>\n </div>\n\n <!-- Apply to wells button -->\n <div v-if=\"hasTargetWells\" class=\"mint-dose-calculator__form\">\n <BaseButton\n variant=\"primary\"\n full-width\n :disabled=\"!canApplyToWells || disabled\"\n @click=\"handleApplyToWells\"\n >\n Apply to {{ targetWells?.length }} selected well{{ targetWells?.length === 1 ? '' : 's' }}\n </BaseButton>\n </div>\n </div>\n</template>\n","/**\n * Shared Plotly look (SDK control refresh 9a), resolved from the MINT tokens.\n *\n * Plotly cannot read `var(--…)`, so the tokens are resolved to computed values\n * at call time; call it again after a theme change. PlotlyChart applies it by\n * default. For a raw Plotly chart use `{ template: mintPlotlyTemplate() }`.\n */\nimport type { LayoutAxis, Template } from 'plotly.js-dist-min'\n\nimport { SAMPLE_COLOR_SCALE, SAMPLE_GROUP_TOKENS } from './color'\n\n/** Resolve tokens from `element` (default: `<html>`), falling back to `<html>` for detached elements. */\nexport function mintPlotlyTemplate(element?: Element): Template {\n const root = getComputedStyle(document.documentElement)\n const own = element ? getComputedStyle(element) : root\n const token = (name: string): string | undefined =>\n own.getPropertyValue(name).trim() || root.getPropertyValue(name).trim() || undefined\n\n const axis: Partial<LayoutAxis> = {\n gridcolor: token('--border-light'),\n linecolor: token('--border-color'),\n zerolinecolor: token('--border-color'),\n tickfont: { family: token('--font-mono'), size: 11, color: token('--text-secondary') },\n }\n const font = { family: token('--font-sans'), size: 12, color: token('--text-primary') }\n\n return {\n layout: {\n paper_bgcolor: 'rgba(0,0,0,0)',\n plot_bgcolor: 'rgba(0,0,0,0)',\n // Group tokens (`var(--…)` stripped to the property name); the hex scale covers pages without the SDK CSS.\n colorway: SAMPLE_GROUP_TOKENS.map((ref, index) => token(ref.slice(4, -1)) ?? SAMPLE_COLOR_SCALE[index]),\n font,\n xaxis: { ...axis, showline: true },\n yaxis: axis,\n hoverlabel: { bgcolor: token('--bg-card'), bordercolor: token('--border-color'), font },\n },\n }\n}\n\n/** Resolve a CSS `var()` color to its computed value on `element`; any other color passes through. */\nexport function resolveCssColor(color: string, element: Element = document.documentElement): string {\n const name = /^var\\((--[\\w-]+)\\)$/.exec(color.trim())?.[1]\n return name ? getComputedStyle(element).getPropertyValue(name).trim() || color : color\n}\n","<script setup lang=\"ts\">\n/** Wraps a chart with header, toolbar slot, loading overlay, empty state, and legend slot. */\nimport type { ChartLegendItem } from '../types'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport { ChartLine } from 'lucide-vue-next'\n\ninterface Props {\n title?: string\n description?: string\n loading?: boolean\n empty?: boolean\n emptyText?: string\n /** Muted line under the empty title. */\n emptyHint?: string\n /** `frame` fills its parent and uses compact workbench chrome. */\n variant?: 'card' | 'frame'\n /** Series shown as clickable items in the header; a click emits `legend-toggle`. */\n legendItems?: ChartLegendItem[]\n}\n\nwithDefaults(defineProps<Props>(), {\n loading: false,\n empty: false,\n emptyText: 'No data available',\n variant: 'card',\n})\n\nconst emit = defineEmits<{\n 'legend-toggle': [index: number]\n}>()\n\ndefineSlots<{\n default?: () => unknown\n header?: () => unknown\n toolbar?: () => unknown\n subhead?: () => unknown\n empty?: () => unknown\n legend?: () => unknown\n footer?: () => unknown\n}>()\n</script>\n\n<template>\n <div :class=\"['mint-chart', `mint-chart--${variant}`]\">\n <div v-if=\"title || description || $slots.header || $slots.toolbar || legendItems?.length\" class=\"mint-chart__header\">\n <slot name=\"header\">\n <div v-if=\"title || description\" class=\"mint-chart__header-text\">\n <h3 v-if=\"title\" class=\"mint-chart__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-chart__description\">{{ description }}</p>\n </div>\n </slot>\n <div v-if=\"legendItems?.length\" class=\"mint-chart__header-legend\">\n <button\n v-for=\"(item, index) in legendItems\"\n :key=\"index\"\n type=\"button\"\n class=\"mint-chart__legend-item\"\n :class=\"{ 'mint-chart__legend-item--hidden': item.hidden }\"\n :aria-pressed=\"!item.hidden\"\n @click=\"emit('legend-toggle', index)\"\n >\n <span class=\"mint-chart__legend-swatch\" :style=\"{ background: item.color }\" aria-hidden=\"true\" />\n {{ item.label }}\n </button>\n </div>\n <div v-if=\"$slots.toolbar\" class=\"mint-chart__toolbar\">\n <slot name=\"toolbar\" />\n </div>\n </div>\n\n <slot name=\"subhead\" />\n\n <div class=\"mint-chart__body\">\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-chart__loading-overlay\">\n <LoadingSpinner size=\"lg\" />\n </div>\n\n <!-- Empty state -->\n <template v-else-if=\"empty\">\n <slot name=\"empty\">\n <div class=\"mint-chart__empty\">\n <ChartLine :size=\"22\" aria-hidden=\"true\" />\n <span class=\"mint-chart__empty-title\">{{ emptyText }}</span>\n <span v-if=\"emptyHint\" class=\"mint-chart__empty-hint\">{{ emptyHint }}</span>\n </div>\n </slot>\n </template>\n\n <!-- Chart content -->\n <template v-else>\n <slot />\n </template>\n </div>\n\n <div v-if=\"$slots.legend\" class=\"mint-chart__legend\">\n <slot name=\"legend\" />\n </div>\n\n <div v-if=\"$slots.footer\" class=\"mint-chart__footer\">\n <slot name=\"footer\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Wraps a chart with header, toolbar slot, loading overlay, empty state, and legend slot. */\nimport type { ChartLegendItem } from '../types'\nimport LoadingSpinner from './LoadingSpinner.vue'\nimport { ChartLine } from 'lucide-vue-next'\n\ninterface Props {\n title?: string\n description?: string\n loading?: boolean\n empty?: boolean\n emptyText?: string\n /** Muted line under the empty title. */\n emptyHint?: string\n /** `frame` fills its parent and uses compact workbench chrome. */\n variant?: 'card' | 'frame'\n /** Series shown as clickable items in the header; a click emits `legend-toggle`. */\n legendItems?: ChartLegendItem[]\n}\n\nwithDefaults(defineProps<Props>(), {\n loading: false,\n empty: false,\n emptyText: 'No data available',\n variant: 'card',\n})\n\nconst emit = defineEmits<{\n 'legend-toggle': [index: number]\n}>()\n\ndefineSlots<{\n default?: () => unknown\n header?: () => unknown\n toolbar?: () => unknown\n subhead?: () => unknown\n empty?: () => unknown\n legend?: () => unknown\n footer?: () => unknown\n}>()\n</script>\n\n<template>\n <div :class=\"['mint-chart', `mint-chart--${variant}`]\">\n <div v-if=\"title || description || $slots.header || $slots.toolbar || legendItems?.length\" class=\"mint-chart__header\">\n <slot name=\"header\">\n <div v-if=\"title || description\" class=\"mint-chart__header-text\">\n <h3 v-if=\"title\" class=\"mint-chart__title\">{{ title }}</h3>\n <p v-if=\"description\" class=\"mint-chart__description\">{{ description }}</p>\n </div>\n </slot>\n <div v-if=\"legendItems?.length\" class=\"mint-chart__header-legend\">\n <button\n v-for=\"(item, index) in legendItems\"\n :key=\"index\"\n type=\"button\"\n class=\"mint-chart__legend-item\"\n :class=\"{ 'mint-chart__legend-item--hidden': item.hidden }\"\n :aria-pressed=\"!item.hidden\"\n @click=\"emit('legend-toggle', index)\"\n >\n <span class=\"mint-chart__legend-swatch\" :style=\"{ background: item.color }\" aria-hidden=\"true\" />\n {{ item.label }}\n </button>\n </div>\n <div v-if=\"$slots.toolbar\" class=\"mint-chart__toolbar\">\n <slot name=\"toolbar\" />\n </div>\n </div>\n\n <slot name=\"subhead\" />\n\n <div class=\"mint-chart__body\">\n <!-- Loading overlay -->\n <div v-if=\"loading\" class=\"mint-chart__loading-overlay\">\n <LoadingSpinner size=\"lg\" />\n </div>\n\n <!-- Empty state -->\n <template v-else-if=\"empty\">\n <slot name=\"empty\">\n <div class=\"mint-chart__empty\">\n <ChartLine :size=\"22\" aria-hidden=\"true\" />\n <span class=\"mint-chart__empty-title\">{{ emptyText }}</span>\n <span v-if=\"emptyHint\" class=\"mint-chart__empty-hint\">{{ emptyHint }}</span>\n </div>\n </slot>\n </template>\n\n <!-- Chart content -->\n <template v-else>\n <slot />\n </template>\n </div>\n\n <div v-if=\"$slots.legend\" class=\"mint-chart__legend\">\n <slot name=\"legend\" />\n </div>\n\n <div v-if=\"$slots.footer\" class=\"mint-chart__footer\">\n <slot name=\"footer\" />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Render native Plotly data with MINT loading, empty, theme, and error states.\n *\n * Plotly is loaded only when the component is mounted, so importing the SDK does\n * not add Plotly to the application's initial bundle.\n *\n * @example\n * ```vue\n * <PlotlyChart\n * title=\"QC overview\"\n * :data=\"traces\"\n * :layout=\"{ xaxis: { title: { text: 'Sample' } } }\"\n * :config=\"{ toImageButtonOptions: { filename: 'qc-overview' } }\"\n * />\n * ```\n */\nimport type * as PlotlyType from 'plotly.js-dist-min'\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'\n\nimport type { ChartLegendItem } from '../types'\nimport { SAMPLE_GROUP_TOKENS } from '../utils/color'\nimport { mintPlotlyTemplate, resolveCssColor } from '../utils/plotlyTemplate'\nimport ChartContainer from './ChartContainer.vue'\n\ninterface Props {\n data: PlotlyType.Data[]\n layout?: Partial<PlotlyType.Layout>\n config?: Partial<PlotlyType.Config>\n title?: string\n description?: string\n loading?: boolean\n empty?: boolean\n emptyText?: string\n ariaLabel?: string\n /** Use a custom Plotly build instead of the SDK's lazy-loaded default bundle. */\n plotly?: typeof PlotlyType\n /** Pause rendering and resize work while a tab or panel is inactive. */\n active?: boolean\n /** Plot canvas height. Numbers are interpreted as pixels. */\n height?: string | number\n /** Forward Plotly point clicks through the `plotly-click` event. */\n clickEvents?: boolean\n /** Shared chart container chrome. `frame` fills a workbench panel. */\n variant?: 'card' | 'frame'\n /**\n * With two or more legend traces, list them in the chart header (click toggles\n * the trace) instead of Plotly's in-plot legend. Any `layout.showlegend` leaves the\n * legend to Plotly (`true` shows its legend, `false` shows none).\n */\n headerLegend?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n layout: () => ({}),\n config: () => ({}),\n loading: false,\n empty: false,\n emptyText: 'No data available',\n ariaLabel: 'Interactive Plotly chart',\n active: true,\n clickEvents: false,\n variant: 'card',\n headerLegend: true,\n})\n\nconst emit = defineEmits<{\n 'plotly-click': [event: PlotlyType.PlotMouseEvent]\n}>()\n\ndefineSlots<{\n header?: () => unknown\n toolbar?: () => unknown\n subhead?: () => unknown\n empty?: () => unknown\n error?: () => unknown\n legend?: () => unknown\n footer?: () => unknown\n}>()\n\nconst chartElement = ref<HTMLDivElement | null>(null)\nconst failed = ref(false)\nlet plotly: typeof PlotlyType | null = props.plotly ?? null\nlet resizeObserver: ResizeObserver | null = null\nlet themeObserver: MutationObserver | null = null\nlet renderedElement: HTMLDivElement | null = null\nlet rendering = false\nlet disposed = false\nlet revision = 0\n\nasync function loadPlotly(): Promise<typeof PlotlyType> {\n if (props.plotly) return props.plotly\n const module = await import('plotly.js-dist-min')\n return ('default' in module ? module.default : module) as unknown as typeof PlotlyType\n}\n\ntype PlotlyEventHost = HTMLDivElement & {\n on?: (eventName: string, callback: (event: PlotlyType.PlotMouseEvent) => void) => void\n removeAllListeners?: (eventName: string) => void\n}\n\nfunction bindEvents(element: HTMLDivElement): void {\n const host = element as PlotlyEventHost\n host.removeAllListeners?.('plotly_click')\n if (props.clickEvents) {\n host.on?.('plotly_click', event => emit('plotly-click', event))\n }\n}\n\n// Unwrap nested Vue proxies while copying Plotly's mutable input tree. Keep\n// native dates/typed arrays and callbacks instead of round-tripping through JSON.\nfunction clonePlotlyInput<T>(value: T): T {\n const raw = toRaw(value)\n if (Array.isArray(raw)) return raw.map(clonePlotlyInput) as T\n if (raw !== null && typeof raw === 'object') {\n if (Object.getPrototypeOf(raw) === Object.prototype) {\n return Object.fromEntries(Object.entries(raw).map(([key, item]) => [key, clonePlotlyInput(item)])) as T\n }\n return structuredClone(raw)\n }\n return raw\n}\n\ntype LegendTrace = {\n name?: string\n showlegend?: boolean\n visible?: boolean | 'legendonly'\n marker?: { color?: unknown }\n line?: { color?: unknown }\n}\n\n/**\n * Header-legend clicks, keyed by label plus its occurrence (`Replicate`, `Replicate#2`), so a data\n * refresh keeps them and same-named traces stay separate. `true` = hidden, `false` = shown.\n */\nconst toggledTraces = ref<Record<string, boolean>>({})\n\n/** Traces listed in the header legend, or none when Plotly keeps its own legend. */\nconst legendTraces = computed(() => {\n if (!props.headerLegend || props.layout.showlegend !== undefined) return []\n const seen: Record<string, number> = {}\n const traces = props.data\n .map((trace, index) => ({ trace: trace as LegendTrace, index }))\n .filter(({ trace }) => trace.showlegend !== false && trace.visible !== false)\n .map(({ trace, index }) => {\n const label = trace.name ?? `Trace ${index + 1}`\n seen[label] = (seen[label] ?? 0) + 1\n return { trace, index, label, key: seen[label] > 1 ? `${label}#${seen[label]}` : label }\n })\n return traces.length > 1 ? traces : []\n})\n\nconst legendItems = computed<ChartLegendItem[]>(() => legendTraces.value.map(({ trace, index, label, key }) => {\n const layoutColorway = props.layout.colorway?.filter((color): color is string => typeof color === 'string')\n const colorway = layoutColorway?.length ? layoutColorway : SAMPLE_GROUP_TOKENS\n const own = [trace.marker?.color, trace.line?.color].find((color): color is string => typeof color === 'string')\n return {\n label,\n color: own ?? colorway[index % colorway.length],\n hidden: toggledTraces.value[key] ?? trace.visible === 'legendonly',\n }\n}))\n\nfunction toggleTrace(legendIndex: number): void {\n const { key } = legendTraces.value[legendIndex]\n toggledTraces.value = { ...toggledTraces.value, [key]: !legendItems.value[legendIndex].hidden }\n}\n\nfunction chartData(): PlotlyType.Data[] {\n const data = clonePlotlyInput(props.data)\n const visible = new Map(legendTraces.value\n .filter(({ key }) => key in toggledTraces.value)\n .map(({ index, key }) => [index, toggledTraces.value[key] ? 'legendonly' as const : true]))\n return data.map((trace, index) => visible.has(index) ? { ...trace, visible: visible.get(index) } : trace)\n}\n\n// Caller layout wins key by key over the MINT template (read fresh on every render,\n// so a theme change picks up the new tokens).\nfunction themedLayout(): Partial<PlotlyType.Layout> {\n const template = mintPlotlyTemplate(chartElement.value ?? undefined).layout ?? {}\n const layout = clonePlotlyInput(props.layout)\n return {\n autosize: true,\n ...template,\n ...(legendTraces.value.length ? { showlegend: false } : {}),\n ...layout,\n // Plotly cannot read `var(--…)`; resolve a caller colorway's tokens on every render.\n ...(layout.colorway ? { colorway: layout.colorway.map(color => (typeof color === 'string' ? resolveCssColor(color, chartElement.value ?? undefined) : color)) } : {}),\n font: { ...template.font, ...layout.font },\n xaxis: { ...template.xaxis, ...layout.xaxis },\n yaxis: { ...template.yaxis, ...layout.yaxis },\n hoverlabel: { ...template.hoverlabel, ...layout.hoverlabel },\n }\n}\n\nfunction purgeChart(): void {\n resizeObserver?.disconnect()\n if (renderedElement) plotly?.purge(renderedElement)\n renderedElement = null\n}\n\nasync function renderChart(): Promise<void> {\n revision++\n if (rendering || disposed || !props.active || props.loading || props.empty) return\n rendering = true\n try {\n while (!disposed && props.active && !props.loading && !props.empty) {\n const currentRevision = revision\n failed.value = false\n await nextTick()\n if (disposed || !props.active || props.loading || props.empty) break\n try {\n plotly ??= await loadPlotly()\n if (disposed || !props.active || props.loading || props.empty) break\n if (currentRevision !== revision) continue\n const element = chartElement.value\n if (!element) break\n renderedElement = element\n // Plotly mutates traces and nested layout fields; keep caller-owned values intact.\n // Plotly 4 shows an \"Upload to Cloud\" modebar button by default; keep it off.\n await plotly.react(\n element,\n chartData(),\n themedLayout(),\n { responsive: true, displaylogo: false, displayModeBar: false, showSendToCloud: false, ...props.config },\n )\n if (disposed || props.loading || props.empty || element !== chartElement.value) {\n purgeChart()\n } else if (props.active) {\n bindEvents(element)\n resizeObserver?.observe(element)\n }\n } catch {\n purgeChart()\n if (!disposed && currentRevision === revision) failed.value = true\n }\n if (currentRevision === revision) break\n }\n } finally {\n rendering = false\n if (disposed || props.loading || props.empty) purgeChart()\n }\n}\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(() => {\n if (rendering || !props.active || !plotly || !renderedElement) return\n // A tab can become hidden or unmount before Plotly's async resize finishes.\n void Promise.resolve(plotly.Plots.resize(renderedElement)).catch(() => {})\n })\n }\n\n if (typeof MutationObserver !== 'undefined') {\n themeObserver = new MutationObserver(() => void renderChart())\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'style'],\n })\n }\n void renderChart()\n})\n\nwatch(\n [() => props.data, () => props.layout, () => props.config, () => props.loading, () => props.empty, () => props.active, toggledTraces],\n () => {\n if (props.loading || props.empty || !props.active) {\n resizeObserver?.disconnect()\n if ((props.loading || props.empty) && !rendering) purgeChart()\n }\n void renderChart()\n },\n)\n\nwatch(\n () => props.clickEvents,\n () => {\n if (renderedElement) bindEvents(renderedElement)\n },\n)\n\nconst chartStyle = computed(() => {\n if (props.height === undefined) return undefined\n const height = typeof props.height === 'number' ? `${props.height}px` : props.height\n return { height, minHeight: height }\n})\n\nonBeforeUnmount(() => {\n disposed = true\n resizeObserver?.disconnect()\n themeObserver?.disconnect()\n // An in-flight react must finish before its resources can be purged completely.\n if (!rendering) purgeChart()\n})\n</script>\n\n<template>\n <ChartContainer\n :title=\"title\"\n :description=\"description\"\n :loading=\"loading\"\n :empty=\"empty\"\n :empty-text=\"emptyText\"\n :variant=\"variant\"\n :legend-items=\"legendItems\"\n @legend-toggle=\"toggleTrace\"\n >\n <template v-if=\"$slots.header\" #header>\n <slot name=\"header\" />\n </template>\n <template v-if=\"$slots.toolbar\" #toolbar>\n <slot name=\"toolbar\" />\n </template>\n <template v-if=\"$slots.subhead\" #subhead>\n <slot name=\"subhead\" />\n </template>\n <template v-if=\"$slots.empty\" #empty>\n <slot name=\"empty\" />\n </template>\n\n <template v-if=\"failed\">\n <slot name=\"error\">\n <div class=\"mint-plotly-chart__error\">\n <p role=\"alert\">Unable to display this chart.</p>\n </div>\n </slot>\n </template>\n <div\n ref=\"chartElement\"\n v-show=\"!failed\"\n class=\"mint-plotly-chart__plot\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :style=\"chartStyle\"\n />\n\n <template v-if=\"$slots.legend\" #legend>\n <slot name=\"legend\" />\n </template>\n <template v-if=\"$slots.footer\" #footer>\n <slot name=\"footer\" />\n </template>\n </ChartContainer>\n</template>\n\n<style scoped>\n.mint-plotly-chart__plot {\n width: 100%;\n min-height: 24rem;\n}\n\n/* Plotly fills the hover label's trace-name box from plot/paper bgcolor combined over\n white (inline style, no hoverlabel option), so it stays light in dark mode. The token\n follows the theme; in light mode it resolves to the same white. */\n.mint-plotly-chart__plot :deep(.hovertext rect) {\n fill: var(--bg-card) !important;\n}\n\n.mint-plotly-chart__error {\n margin: 0;\n padding: 1rem;\n border: 1px solid var(--mint-error-border);\n border-radius: var(--radius-lg);\n background: var(--mint-error-bg);\n color: var(--mint-error-hover);\n}\n\n.mint-plotly-chart__error :deep(p) {\n margin: 0;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * Render native Plotly data with MINT loading, empty, theme, and error states.\n *\n * Plotly is loaded only when the component is mounted, so importing the SDK does\n * not add Plotly to the application's initial bundle.\n *\n * @example\n * ```vue\n * <PlotlyChart\n * title=\"QC overview\"\n * :data=\"traces\"\n * :layout=\"{ xaxis: { title: { text: 'Sample' } } }\"\n * :config=\"{ toImageButtonOptions: { filename: 'qc-overview' } }\"\n * />\n * ```\n */\nimport type * as PlotlyType from 'plotly.js-dist-min'\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, toRaw, watch } from 'vue'\n\nimport type { ChartLegendItem } from '../types'\nimport { SAMPLE_GROUP_TOKENS } from '../utils/color'\nimport { mintPlotlyTemplate, resolveCssColor } from '../utils/plotlyTemplate'\nimport ChartContainer from './ChartContainer.vue'\n\ninterface Props {\n data: PlotlyType.Data[]\n layout?: Partial<PlotlyType.Layout>\n config?: Partial<PlotlyType.Config>\n title?: string\n description?: string\n loading?: boolean\n empty?: boolean\n emptyText?: string\n ariaLabel?: string\n /** Use a custom Plotly build instead of the SDK's lazy-loaded default bundle. */\n plotly?: typeof PlotlyType\n /** Pause rendering and resize work while a tab or panel is inactive. */\n active?: boolean\n /** Plot canvas height. Numbers are interpreted as pixels. */\n height?: string | number\n /** Forward Plotly point clicks through the `plotly-click` event. */\n clickEvents?: boolean\n /** Shared chart container chrome. `frame` fills a workbench panel. */\n variant?: 'card' | 'frame'\n /**\n * With two or more legend traces, list them in the chart header (click toggles\n * the trace) instead of Plotly's in-plot legend. Any `layout.showlegend` leaves the\n * legend to Plotly (`true` shows its legend, `false` shows none).\n */\n headerLegend?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n layout: () => ({}),\n config: () => ({}),\n loading: false,\n empty: false,\n emptyText: 'No data available',\n ariaLabel: 'Interactive Plotly chart',\n active: true,\n clickEvents: false,\n variant: 'card',\n headerLegend: true,\n})\n\nconst emit = defineEmits<{\n 'plotly-click': [event: PlotlyType.PlotMouseEvent]\n}>()\n\ndefineSlots<{\n header?: () => unknown\n toolbar?: () => unknown\n subhead?: () => unknown\n empty?: () => unknown\n error?: () => unknown\n legend?: () => unknown\n footer?: () => unknown\n}>()\n\nconst chartElement = ref<HTMLDivElement | null>(null)\nconst failed = ref(false)\nlet plotly: typeof PlotlyType | null = props.plotly ?? null\nlet resizeObserver: ResizeObserver | null = null\nlet themeObserver: MutationObserver | null = null\nlet renderedElement: HTMLDivElement | null = null\nlet rendering = false\nlet disposed = false\nlet revision = 0\n\nasync function loadPlotly(): Promise<typeof PlotlyType> {\n if (props.plotly) return props.plotly\n const module = await import('plotly.js-dist-min')\n return ('default' in module ? module.default : module) as unknown as typeof PlotlyType\n}\n\ntype PlotlyEventHost = HTMLDivElement & {\n on?: (eventName: string, callback: (event: PlotlyType.PlotMouseEvent) => void) => void\n removeAllListeners?: (eventName: string) => void\n}\n\nfunction bindEvents(element: HTMLDivElement): void {\n const host = element as PlotlyEventHost\n host.removeAllListeners?.('plotly_click')\n if (props.clickEvents) {\n host.on?.('plotly_click', event => emit('plotly-click', event))\n }\n}\n\n// Unwrap nested Vue proxies while copying Plotly's mutable input tree. Keep\n// native dates/typed arrays and callbacks instead of round-tripping through JSON.\nfunction clonePlotlyInput<T>(value: T): T {\n const raw = toRaw(value)\n if (Array.isArray(raw)) return raw.map(clonePlotlyInput) as T\n if (raw !== null && typeof raw === 'object') {\n if (Object.getPrototypeOf(raw) === Object.prototype) {\n return Object.fromEntries(Object.entries(raw).map(([key, item]) => [key, clonePlotlyInput(item)])) as T\n }\n return structuredClone(raw)\n }\n return raw\n}\n\ntype LegendTrace = {\n name?: string\n showlegend?: boolean\n visible?: boolean | 'legendonly'\n marker?: { color?: unknown }\n line?: { color?: unknown }\n}\n\n/**\n * Header-legend clicks, keyed by label plus its occurrence (`Replicate`, `Replicate#2`), so a data\n * refresh keeps them and same-named traces stay separate. `true` = hidden, `false` = shown.\n */\nconst toggledTraces = ref<Record<string, boolean>>({})\n\n/** Traces listed in the header legend, or none when Plotly keeps its own legend. */\nconst legendTraces = computed(() => {\n if (!props.headerLegend || props.layout.showlegend !== undefined) return []\n const seen: Record<string, number> = {}\n const traces = props.data\n .map((trace, index) => ({ trace: trace as LegendTrace, index }))\n .filter(({ trace }) => trace.showlegend !== false && trace.visible !== false)\n .map(({ trace, index }) => {\n const label = trace.name ?? `Trace ${index + 1}`\n seen[label] = (seen[label] ?? 0) + 1\n return { trace, index, label, key: seen[label] > 1 ? `${label}#${seen[label]}` : label }\n })\n return traces.length > 1 ? traces : []\n})\n\nconst legendItems = computed<ChartLegendItem[]>(() => legendTraces.value.map(({ trace, index, label, key }) => {\n const layoutColorway = props.layout.colorway?.filter((color): color is string => typeof color === 'string')\n const colorway = layoutColorway?.length ? layoutColorway : SAMPLE_GROUP_TOKENS\n const own = [trace.marker?.color, trace.line?.color].find((color): color is string => typeof color === 'string')\n return {\n label,\n color: own ?? colorway[index % colorway.length],\n hidden: toggledTraces.value[key] ?? trace.visible === 'legendonly',\n }\n}))\n\nfunction toggleTrace(legendIndex: number): void {\n const { key } = legendTraces.value[legendIndex]\n toggledTraces.value = { ...toggledTraces.value, [key]: !legendItems.value[legendIndex].hidden }\n}\n\nfunction chartData(): PlotlyType.Data[] {\n const data = clonePlotlyInput(props.data)\n const visible = new Map(legendTraces.value\n .filter(({ key }) => key in toggledTraces.value)\n .map(({ index, key }) => [index, toggledTraces.value[key] ? 'legendonly' as const : true]))\n return data.map((trace, index) => visible.has(index) ? { ...trace, visible: visible.get(index) } : trace)\n}\n\n// Caller layout wins key by key over the MINT template (read fresh on every render,\n// so a theme change picks up the new tokens).\nfunction themedLayout(): Partial<PlotlyType.Layout> {\n const template = mintPlotlyTemplate(chartElement.value ?? undefined).layout ?? {}\n const layout = clonePlotlyInput(props.layout)\n return {\n autosize: true,\n ...template,\n ...(legendTraces.value.length ? { showlegend: false } : {}),\n ...layout,\n // Plotly cannot read `var(--…)`; resolve a caller colorway's tokens on every render.\n ...(layout.colorway ? { colorway: layout.colorway.map(color => (typeof color === 'string' ? resolveCssColor(color, chartElement.value ?? undefined) : color)) } : {}),\n font: { ...template.font, ...layout.font },\n xaxis: { ...template.xaxis, ...layout.xaxis },\n yaxis: { ...template.yaxis, ...layout.yaxis },\n hoverlabel: { ...template.hoverlabel, ...layout.hoverlabel },\n }\n}\n\nfunction purgeChart(): void {\n resizeObserver?.disconnect()\n if (renderedElement) plotly?.purge(renderedElement)\n renderedElement = null\n}\n\nasync function renderChart(): Promise<void> {\n revision++\n if (rendering || disposed || !props.active || props.loading || props.empty) return\n rendering = true\n try {\n while (!disposed && props.active && !props.loading && !props.empty) {\n const currentRevision = revision\n failed.value = false\n await nextTick()\n if (disposed || !props.active || props.loading || props.empty) break\n try {\n plotly ??= await loadPlotly()\n if (disposed || !props.active || props.loading || props.empty) break\n if (currentRevision !== revision) continue\n const element = chartElement.value\n if (!element) break\n renderedElement = element\n // Plotly mutates traces and nested layout fields; keep caller-owned values intact.\n // Plotly 4 shows an \"Upload to Cloud\" modebar button by default; keep it off.\n await plotly.react(\n element,\n chartData(),\n themedLayout(),\n { responsive: true, displaylogo: false, displayModeBar: false, showSendToCloud: false, ...props.config },\n )\n if (disposed || props.loading || props.empty || element !== chartElement.value) {\n purgeChart()\n } else if (props.active) {\n bindEvents(element)\n resizeObserver?.observe(element)\n }\n } catch {\n purgeChart()\n if (!disposed && currentRevision === revision) failed.value = true\n }\n if (currentRevision === revision) break\n }\n } finally {\n rendering = false\n if (disposed || props.loading || props.empty) purgeChart()\n }\n}\n\nonMounted(() => {\n if (typeof ResizeObserver !== 'undefined') {\n resizeObserver = new ResizeObserver(() => {\n if (rendering || !props.active || !plotly || !renderedElement) return\n // A tab can become hidden or unmount before Plotly's async resize finishes.\n void Promise.resolve(plotly.Plots.resize(renderedElement)).catch(() => {})\n })\n }\n\n if (typeof MutationObserver !== 'undefined') {\n themeObserver = new MutationObserver(() => void renderChart())\n themeObserver.observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class', 'style'],\n })\n }\n void renderChart()\n})\n\nwatch(\n [() => props.data, () => props.layout, () => props.config, () => props.loading, () => props.empty, () => props.active, toggledTraces],\n () => {\n if (props.loading || props.empty || !props.active) {\n resizeObserver?.disconnect()\n if ((props.loading || props.empty) && !rendering) purgeChart()\n }\n void renderChart()\n },\n)\n\nwatch(\n () => props.clickEvents,\n () => {\n if (renderedElement) bindEvents(renderedElement)\n },\n)\n\nconst chartStyle = computed(() => {\n if (props.height === undefined) return undefined\n const height = typeof props.height === 'number' ? `${props.height}px` : props.height\n return { height, minHeight: height }\n})\n\nonBeforeUnmount(() => {\n disposed = true\n resizeObserver?.disconnect()\n themeObserver?.disconnect()\n // An in-flight react must finish before its resources can be purged completely.\n if (!rendering) purgeChart()\n})\n</script>\n\n<template>\n <ChartContainer\n :title=\"title\"\n :description=\"description\"\n :loading=\"loading\"\n :empty=\"empty\"\n :empty-text=\"emptyText\"\n :variant=\"variant\"\n :legend-items=\"legendItems\"\n @legend-toggle=\"toggleTrace\"\n >\n <template v-if=\"$slots.header\" #header>\n <slot name=\"header\" />\n </template>\n <template v-if=\"$slots.toolbar\" #toolbar>\n <slot name=\"toolbar\" />\n </template>\n <template v-if=\"$slots.subhead\" #subhead>\n <slot name=\"subhead\" />\n </template>\n <template v-if=\"$slots.empty\" #empty>\n <slot name=\"empty\" />\n </template>\n\n <template v-if=\"failed\">\n <slot name=\"error\">\n <div class=\"mint-plotly-chart__error\">\n <p role=\"alert\">Unable to display this chart.</p>\n </div>\n </slot>\n </template>\n <div\n ref=\"chartElement\"\n v-show=\"!failed\"\n class=\"mint-plotly-chart__plot\"\n role=\"group\"\n :aria-label=\"ariaLabel\"\n :style=\"chartStyle\"\n />\n\n <template v-if=\"$slots.legend\" #legend>\n <slot name=\"legend\" />\n </template>\n <template v-if=\"$slots.footer\" #footer>\n <slot name=\"footer\" />\n </template>\n </ChartContainer>\n</template>\n\n<style scoped>\n.mint-plotly-chart__plot {\n width: 100%;\n min-height: 24rem;\n}\n\n/* Plotly fills the hover label's trace-name box from plot/paper bgcolor combined over\n white (inline style, no hoverlabel option), so it stays light in dark mode. The token\n follows the theme; in light mode it resolves to the same white. */\n.mint-plotly-chart__plot :deep(.hovertext rect) {\n fill: var(--bg-card) !important;\n}\n\n.mint-plotly-chart__error {\n margin: 0;\n padding: 1rem;\n border: 1px solid var(--mint-error-border);\n border-radius: var(--radius-lg);\n background: var(--mint-error-bg);\n color: var(--mint-error-hover);\n}\n\n.mint-plotly-chart__error :deep(p) {\n margin: 0;\n}\n</style>\n","import { getInjectedPlatformContext } from './platformContextHelpers'\nimport { useExperimentStore } from '../stores/experiment'\nimport type {\n BuildEndpointUrlOptions,\n PluginContract,\n PluginEndpointDefinition,\n PluginEndpointParamDefinition,\n} from './createClient'\n\nfunction normalizeApiBaseUrl(baseUrl: string | undefined): string | undefined {\n if (!baseUrl) return undefined\n return baseUrl.length > 1 ? baseUrl.replace(/\\/+$/, '') : baseUrl\n}\n\nfunction normalizeApiPrefix(prefix: string | undefined): string | undefined {\n if (!prefix) return undefined\n if (prefix.startsWith('/api/')) return normalizeApiBaseUrl(prefix)\n if (prefix === '/api') return prefix\n if (prefix.startsWith('/')) return normalizeApiBaseUrl(`/api${prefix}`)\n return normalizeApiBaseUrl(`/api/${prefix}`)\n}\n\nfunction endpointParamName(param: PluginEndpointParamDefinition): string {\n return typeof param === 'string' ? param : param.name\n}\n\nfunction endpointParamFieldName(param: PluginEndpointParamDefinition): string {\n return typeof param === 'string' ? param : param.fieldName ?? param.name\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\nfunction replacePathParam(path: string, name: string, encoded: string): string {\n const escapedName = escapeRegExp(name)\n return path\n .replace(new RegExp(`\\\\{${escapedName}(?::[^{}]+)?\\\\}`, 'g'), () => encoded)\n .replace(new RegExp(`:${escapedName}(?=/|$)`, 'g'), () => encoded)\n}\n\n/** Resolve the runtime plugin API base URL from env, explicit options, platform injection, or contract metadata. */\nexport function resolvePluginBaseUrl(contract: PluginContract, explicitBaseUrl?: string): string {\n const envPrefix = (import.meta.env?.VITE_API_PREFIX as string | undefined) || undefined\n if (envPrefix) return normalizeApiBaseUrl(envPrefix) ?? envPrefix\n if (explicitBaseUrl) return normalizeApiBaseUrl(explicitBaseUrl) ?? explicitBaseUrl\n\n const injected = getInjectedPlatformContext()\n const platformPrefix =\n normalizeApiBaseUrl(injected?.plugin?.api_prefix) ||\n normalizeApiPrefix(injected?.plugin?.route_prefix)\n if (platformPrefix) return platformPrefix\n\n return normalizeApiBaseUrl(contract.plugin.apiPrefix) || '/api'\n}\n\nfunction encodePath(\n path: string,\n payload: Record<string, unknown>,\n pathParams: PluginEndpointParamDefinition[],\n): string {\n let nextPath = path\n for (const param of pathParams) {\n const name = endpointParamName(param)\n const fieldName = endpointParamFieldName(param)\n const value = payload[fieldName] ?? payload[name]\n if (value === undefined || value === null) {\n throw new Error(`[MINT SDK] Missing path parameter '${fieldName}' for plugin endpoint ${path}`)\n }\n const encoded = encodeURIComponent(String(value))\n nextPath = replacePathParam(nextPath, name, encoded)\n if (fieldName !== name) {\n nextPath = replacePathParam(nextPath, fieldName, encoded)\n }\n }\n return nextPath\n}\n\nfunction withDefaultPathParams(\n payload: Record<string, unknown>,\n pathParams: PluginEndpointParamDefinition[],\n): Record<string, unknown> {\n if (!pathParams.length) return payload\n const nextPayload = { ...payload }\n for (const param of pathParams) {\n const name = endpointParamName(param)\n const fieldName = endpointParamFieldName(param)\n if (\n (nextPayload[fieldName] !== undefined && nextPayload[fieldName] !== null)\n || (nextPayload[name] !== undefined && nextPayload[name] !== null)\n ) {\n continue\n }\n if (isExperimentPathParam(fieldName) || isExperimentPathParam(name)) {\n const experimentId = useExperimentStore().currentId\n if (experimentId !== null) {\n nextPayload[fieldName] = experimentId\n }\n }\n }\n return nextPayload\n}\n\nfunction isExperimentPathParam(param: string): boolean {\n return param === 'experimentId' || param === 'experiment_id'\n}\n\nfunction withoutPathParams(payload: Record<string, unknown>, pathParams: PluginEndpointParamDefinition[]) {\n const copy = { ...payload }\n for (const param of pathParams) {\n delete copy[endpointParamFieldName(param)]\n delete copy[endpointParamName(param)]\n }\n return copy\n}\n\nfunction queryParamsFromPayload(\n payload: Record<string, unknown>,\n pathParams: PluginEndpointParamDefinition[],\n queryParams?: PluginEndpointParamDefinition[],\n endpointPath?: string,\n includeUnspecifiedParams = false,\n) {\n if (!queryParams?.length) {\n return includeUnspecifiedParams ? withoutPathParams(payload, pathParams) : {}\n }\n\n const query: Record<string, unknown> = {}\n for (const param of queryParams) {\n const name = typeof param === 'string' ? param : param.name\n const fieldName = typeof param === 'string' ? param : param.fieldName ?? param.name\n const required = typeof param !== 'string' && param.required === true\n const value = payload[fieldName]\n if (value === undefined || value === null) {\n if (required) {\n throw new Error(\n `[MINT SDK] Missing query parameter '${fieldName}' for plugin endpoint ${endpointPath ?? ''}`,\n )\n }\n continue\n }\n query[name] = value\n }\n return query\n}\n\nexport function hasKeys(value: Record<string, unknown>): boolean {\n return Object.keys(value).length > 0\n}\n\nfunction isPlainPayloadObject(value: unknown): value is Record<string, unknown> {\n return !!value && typeof value === 'object' && !Array.isArray(value)\n}\n\nfunction hasOwn(value: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(value, key)\n}\n\nfunction normalizeStructuredEndpointPayload(payload: Record<string, unknown>): Record<string, unknown> {\n const pathParamPayload = payload.pathParams\n const queryPayload = payload.query\n const hasStructuredPathParams = hasOwn(payload, 'pathParams') && isPlainPayloadObject(pathParamPayload)\n const hasStructuredQuery = hasOwn(payload, 'query') && isPlainPayloadObject(queryPayload)\n if (!hasStructuredPathParams && !hasStructuredQuery) return payload\n\n const nextPayload = { ...payload }\n delete nextPayload.pathParams\n delete nextPayload.query\n\n if (hasStructuredPathParams) {\n Object.assign(nextPayload, pathParamPayload)\n }\n if (hasStructuredQuery) {\n Object.assign(nextPayload, queryPayload)\n }\n\n return nextPayload\n}\n\nexport function requestBodyFromPayload(\n payload: unknown,\n requestPayload: Record<string, unknown>,\n endpoint: PluginEndpointDefinition,\n): unknown {\n if (!endpoint.hasBody) return undefined\n const body = endpoint.pathParams?.length || endpoint.queryParams?.length\n ? requestPayload.body\n : payload\n if (body === undefined || body === null) {\n throw new Error(`[MINT SDK] Missing request body for plugin endpoint ${endpoint.path}`)\n }\n return body\n}\n\nexport function pluginEndpointRequestParts(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n payload?: unknown,\n explicitBaseUrl?: string,\n) {\n const payloadObject = isPlainPayloadObject(payload)\n ? normalizeStructuredEndpointPayload(payload)\n : {}\n const pathParams = endpoint.pathParams ?? []\n const requestPayload = withDefaultPathParams(payloadObject, pathParams)\n const path = encodePath(endpoint.path, requestPayload, pathParams)\n const query = queryParamsFromPayload(\n requestPayload,\n pathParams,\n endpoint.queryParams,\n endpoint.path,\n endpoint.method === 'get',\n )\n\n return {\n baseUrl: resolvePluginBaseUrl(contract, explicitBaseUrl),\n path,\n query,\n requestPayload,\n }\n}\n\nfunction appendQueryValue(params: URLSearchParams, key: string, value: unknown): void {\n if (value === undefined || value === null) return\n if (Array.isArray(value)) {\n for (const item of value) {\n appendQueryValue(params, key, item)\n }\n return\n }\n if (typeof value === 'object') {\n params.append(key, JSON.stringify(value))\n return\n }\n params.append(key, String(value))\n}\n\nfunction queryString(query: Record<string, unknown>): string {\n const params = new URLSearchParams()\n for (const [key, value] of Object.entries(query)) {\n appendQueryValue(params, key, value)\n }\n const serialized = params.toString()\n return serialized ? `?${serialized}` : ''\n}\n\nfunction joinBaseUrl(baseUrl: string, path: string): string {\n if (!baseUrl) return path\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl\n const normalizedPath = path.startsWith('/') ? path : `/${path}`\n return `${normalizedBase}${normalizedPath}`\n}\n\n/** Build the concrete URL for a generated plugin endpoint without making a request. */\nexport function buildEndpointUrl(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n payload?: unknown,\n options: BuildEndpointUrlOptions = {},\n): string {\n const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)\n const pathWithQuery = `${parts.path}${queryString(parts.query)}`\n if (options.includeBaseUrl === false) return pathWithQuery\n return joinBaseUrl(parts.baseUrl, pathWithQuery)\n}\n","import {\n computed,\n onMounted,\n ref,\n shallowRef,\n type ComputedRef,\n type Ref,\n} from 'vue'\nimport {\n AuthenticationRequiredError,\n authenticationRequiredError,\n useApi,\n} from './useApi'\nimport { usePlatform } from './usePlatform'\nimport { useRequest } from './useRequest'\nimport { useAuthStore } from '../stores/auth'\nimport * as pluginSecret from '../utils/pluginSecrets'\nimport { getInjectedPlatformContext } from './platformContextHelpers'\nimport {\n buildEndpointUrl,\n hasKeys,\n pluginEndpointRequestParts,\n requestBodyFromPayload,\n} from './pluginEndpointBuilder'\nimport {\n createEventStream,\n type EventStreamMessage,\n type EventStreamTransport,\n type UseEventStreamReturn,\n} from './eventStream'\nimport type {\n GeneratedJobDefinition,\n SettingsModalSchema,\n TopBarSettingsConfig,\n} from '../types'\n\nexport {\n buildEndpointUrl,\n resolvePluginBaseUrl,\n} from './pluginEndpointBuilder'\n\nexport type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'\n\nexport interface PluginEndpointContract {\n name: string\n operationId?: string\n method: PluginHttpMethod\n path: string\n requiresAuth?: boolean\n pathParams?: PluginEndpointParamContract[]\n queryParams?: PluginEndpointParamContract[]\n requestType?: string | null\n requestContentType?: string | null\n responseType?: string | null\n responseContentType?: string | null\n eventStream?: boolean\n}\n\nexport interface PluginEndpointParamContract {\n name: string\n fieldName: string\n type?: string\n required?: boolean\n}\n\nexport interface PluginNavItemContract {\n path: string\n label: string\n id?: string\n icon?: string\n description?: string\n requiresAuth?: boolean\n access?: 'use' | 'configure' | 'admin'\n requiresFeature?: string\n}\n\nexport interface PluginContractSettings {\n modelName: string | null\n /** Pydantic JSON Schema used to generate typed settings values. */\n valueSchema: Record<string, unknown> | null\n /** SDK SettingsModal schema used to render the settings form. */\n formSchema: SettingsModalSchema | null\n}\n\nexport interface PluginContract {\n schemaVersion: 2\n plugin: {\n name?: string\n displayName?: string\n subtitle?: string\n packageName?: string\n version?: string\n description?: string\n routesPrefix: string\n apiPrefix: string\n type: 'analysis' | 'experiment-design' | string\n analysisType?: string\n icon?: string\n color?: string\n navItems?: PluginNavItemContract[]\n analysisResultReaders?: string[]\n analysisResultWriters?: string[]\n capabilities?: Record<string, unknown>\n }\n settings: PluginContractSettings\n endpoints: PluginEndpointContract[]\n schemas?: Record<string, Record<string, unknown>>\n jobs?: GeneratedJobDefinition[]\n hash: string\n}\n\nexport interface PluginEndpointDefinition {\n method: PluginHttpMethod\n path: string\n requiresAuth?: boolean\n pathParams?: PluginEndpointParamDefinition[]\n queryParams?: PluginEndpointParamDefinition[]\n hasBody?: boolean\n requestContentType?: string | null\n eventStream?: boolean\n responseType?: string | null\n responseContentType?: string | null\n}\n\nexport type PluginEndpointParamDefinition = string | {\n name: string\n fieldName?: string\n type?: string\n required?: boolean\n}\n\nexport interface CreateClientOptions {\n endpoints: Record<string, PluginEndpointDefinition>\n baseUrl?: string\n}\n\nexport interface BuildEndpointUrlOptions {\n /** Override the generated or platform-injected API base URL. */\n baseUrl?: string\n /** Return only the endpoint path and query string instead of base URL + path. */\n includeBaseUrl?: boolean\n}\n\nexport interface PluginEndpointRequestOptions {\n /** Override the generated or platform-injected API base URL. */\n baseUrl?: string\n /** Cancel the request without changing its endpoint or authentication behavior. */\n signal?: AbortSignal\n}\n\nexport interface PluginClientRequestOptions {\n /** Cancel the generated endpoint request. */\n signal?: AbortSignal\n}\n\nexport interface DownloadEndpointOptions extends PluginEndpointRequestOptions {\n /** Browser download filename. When omitted, the blob is returned without triggering a download. */\n filename?: string\n /** Save the blob as `filename` in the browser. Default true; `false` only returns the blob. */\n save?: boolean\n /** Delay before revoking the object URL after a browser download is triggered. */\n revokeObjectUrlDelayMs?: number\n}\n\nexport interface DownloadBlobOptions {\n /** Delay before revoking the object URL after a browser download is triggered. */\n revokeObjectUrlDelayMs?: number\n}\n\nexport type PluginSettingsValues<TSettings> = Partial<TSettings> & Record<string, unknown>\n\nexport interface UseSettingsEndpointOptions {\n /** Plugin name for platform persistence. Defaults to the integrated platform plugin name. */\n pluginName?: string\n /** Plugin API base URL for standalone/local settings routes. Generated clients pass pluginApiPrefix. */\n apiBaseUrl?: string\n /** Platform API base URL for /plugins/{name}/config. Defaults to the injected platform API URL. */\n platformApiBaseUrl?: string\n /** SettingsModal schema generated from the backend plugin config model. */\n schema?: SettingsModalSchema | null\n /** SettingsModal title when using settingsConfig. */\n title?: string\n /** Whether SettingsModal should include SDK appearance controls. */\n showAppearance?: boolean\n /** Load persisted values on component mount. */\n loadOnMount?: boolean\n}\n\nexport interface UseSettingsEndpointReturn<TSettings = Record<string, unknown>> {\n /** Current typed settings values. */\n settings: ComputedRef<PluginSettingsValues<TSettings>>\n /** Editable settings values for SettingsModal. */\n values: Ref<PluginSettingsValues<TSettings>>\n /** Alias for values, kept for callers that think in platform config terms. */\n config: Ref<PluginSettingsValues<TSettings>>\n /** Ready-to-pass AppTopBar/PluginWorkspaceView settingsConfig. */\n settingsConfig: ComputedRef<TopBarSettingsConfig>\n isLoading: Ref<boolean>\n isSaving: Ref<boolean>\n error: Ref<string | null>\n lastLoadedAt: Ref<Date | null>\n lastSavedAt: Ref<Date | null>\n isDirty: ComputedRef<boolean>\n setValues: (values: Record<string, unknown>) => void\n load: () => Promise<void>\n save: (values?: Record<string, unknown>) => Promise<boolean>\n reset: () => void\n}\n\nexport type { EventStreamMessage, UseEventStreamReturn } from './eventStream'\n\nexport interface StreamOptions<TData = string> {\n /** Override the generated or platform-injected API base URL. */\n baseUrl?: string\n /** Start the stream immediately. Defaults to true. */\n immediate?: boolean\n /** Reconnect after errors or server closes. Defaults to true. */\n reconnect?: boolean\n /** Delay before reconnecting after a stream failure or close. Defaults to 2000ms. */\n reconnectDelayMs?: number\n /** Parse `data:` as JSON. Defaults to true for typed endpoints and false otherwise. */\n parseJson?: boolean\n /** Fetch credentials mode. Defaults to same-origin. */\n credentials?: RequestCredentials\n /** Extra request headers. Authorization is injected unless already supplied. */\n headers?: Record<string, string>\n onOpen?: () => void\n onMessage?: (message: EventStreamMessage<TData>) => void\n onError?: (error: unknown) => void\n}\n\nexport type PluginFormDataValue =\n | string\n | number\n | boolean\n | Blob\n | null\n | undefined\n | Record<string, unknown>\n\nexport type PluginFormDataPayload = Record<string, PluginFormDataValue | PluginFormDataValue[]>\n\nexport type PluginEndpointCaller = (\n payloadOrOptions?: unknown,\n options?: PluginClientRequestOptions,\n) => Promise<unknown>\nexport type GeneratedPluginClient = Record<string, (...args: any[]) => Promise<any>>\n\nfunction queryConfig(query: Record<string, unknown>) {\n return hasKeys(query) ? { params: query } : undefined\n}\n\nconst OCTET_STREAM = 'application/octet-stream'\nconst MULTIPART_FORM_DATA = 'multipart/form-data'\n\nfunction endpointRequestConfig(\n endpoint: PluginEndpointDefinition,\n query: Record<string, unknown>,\n options?: PluginClientRequestOptions,\n) {\n const config: Record<string, unknown> = {}\n if (hasKeys(query)) config.params = query\n if (endpoint.requestContentType === OCTET_STREAM) {\n config.headers = { 'Content-Type': OCTET_STREAM }\n }\n if (endpoint.requestContentType === MULTIPART_FORM_DATA) {\n config.headers = { 'Content-Type': undefined }\n }\n if (endpoint.responseContentType === OCTET_STREAM) {\n config.responseType = 'blob'\n }\n if (options?.signal) config.signal = options.signal\n return hasKeys(config) ? config : undefined\n}\n\nfunction isFormData(value: unknown): value is FormData {\n return typeof FormData !== 'undefined' && value instanceof FormData\n}\n\nfunction endpointRequestBody(endpoint: PluginEndpointDefinition, body: unknown): unknown {\n return endpoint.requestContentType === MULTIPART_FORM_DATA\n ? pluginFormDataFromPayload(body)\n : body\n}\n\nfunction endpointHasPayload(endpoint: PluginEndpointDefinition): boolean {\n return Boolean(\n endpoint.hasBody\n || endpoint.pathParams?.length\n || endpoint.queryParams?.length,\n )\n}\n\nfunction isBlob(value: unknown): value is Blob {\n return typeof Blob !== 'undefined' && value instanceof Blob\n}\n\nfunction appendFormDataValue(formData: FormData, key: string, value: PluginFormDataValue | PluginFormDataValue[]): void {\n if (value === undefined || value === null) return\n\n if (Array.isArray(value)) {\n for (const item of value) {\n appendFormDataValue(formData, key, item)\n }\n return\n }\n\n if (isBlob(value)) {\n formData.append(key, value)\n return\n }\n\n if (typeof value === 'object') {\n formData.append(key, JSON.stringify(value))\n return\n }\n\n formData.append(key, String(value))\n}\n\n/** Convert plain upload payloads into FormData using the same encoding across generated plugin clients. */\nexport function pluginFormDataFromPayload(payload: unknown): FormData {\n if (isFormData(payload)) return payload\n if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {\n throw new Error('[MINT SDK] Plugin upload endpoints require a FormData or plain object payload.')\n }\n\n const formData = new FormData()\n for (const [key, value] of Object.entries(payload as PluginFormDataPayload)) {\n appendFormDataValue(formData, key, value)\n }\n return formData\n}\n\n/** Trigger a browser download for an already-created Blob. */\nexport function downloadBlob(blob: Blob, filename: string, options: DownloadBlobOptions = {}): void {\n if (typeof document === 'undefined' || typeof URL === 'undefined') {\n throw new Error('[MINT SDK] Cannot trigger a file download outside a browser.')\n }\n\n const blobUrl = URL.createObjectURL(blob)\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 setTimeout(() => URL.revokeObjectURL(blobUrl), options.revokeObjectUrlDelayMs ?? 100)\n}\n\n/** Create a typed plugin API client from a generated MINT plugin contract. */\nexport function createClient<TClient extends object = GeneratedPluginClient>(\n contract: PluginContract,\n options: CreateClientOptions,\n): TClient {\n const client: Record<string, PluginEndpointCaller> = {}\n\n for (const [name, endpoint] of Object.entries(options.endpoints)) {\n client[name] = async (\n payloadOrOptions?: unknown,\n explicitOptions?: PluginClientRequestOptions,\n ) => {\n const hasPayload = endpointHasPayload(endpoint)\n const payload = hasPayload ? payloadOrOptions : undefined\n const callOptions = hasPayload\n ? explicitOptions\n : (payloadOrOptions as PluginClientRequestOptions | undefined) ?? explicitOptions\n const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)\n const api = useApi({\n baseUrl: parts.baseUrl,\n withAuth: endpoint.requiresAuth !== false,\n })\n const config = endpointRequestConfig(endpoint, parts.query, callOptions)\n\n if (endpoint.method === 'get') {\n return api.get(parts.path, config)\n }\n\n if (endpoint.method === 'delete') {\n const rawBody = requestBodyFromPayload(payload, parts.requestPayload, endpoint)\n const body = rawBody === undefined ? undefined : endpointRequestBody(endpoint, rawBody)\n const config = body === undefined\n ? endpointRequestConfig(endpoint, parts.query, callOptions)\n : { ...endpointRequestConfig(endpoint, parts.query, callOptions), data: body }\n return api.delete(parts.path, config)\n }\n\n const body = endpointRequestBody(\n endpoint,\n requestBodyFromPayload(payload, parts.requestPayload, endpoint),\n )\n\n if (endpoint.method === 'post') return api.post(parts.path, body, config)\n if (endpoint.method === 'put') return api.put(parts.path, body, config)\n if (endpoint.method === 'patch') return api.patch(parts.path, body, config)\n throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`)\n }\n }\n\n return client as TClient\n}\n\n/** Send multipart or raw binary data to a generated endpoint with contract URL, cancellation, and auth handling. */\nexport async function uploadEndpoint<TResponse = unknown>(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n payload?: unknown,\n options: PluginEndpointRequestOptions = {},\n): Promise<TResponse> {\n if (!['post', 'put', 'patch'].includes(endpoint.method)) {\n throw new Error(`[MINT SDK] Plugin upload endpoints must use POST, PUT, or PATCH; got ${endpoint.method}`)\n }\n\n const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)\n const api = useApi({\n baseUrl: parts.baseUrl,\n withAuth: endpoint.requiresAuth !== false,\n })\n const body = endpoint.hasBody\n ? requestBodyFromPayload(payload, parts.requestPayload, endpoint)\n : payload\n if (endpoint.requestContentType === OCTET_STREAM) {\n const config = endpointRequestConfig(endpoint, parts.query, { signal: options.signal })\n if (endpoint.method === 'post') return api.post<TResponse>(parts.path, body, config)\n if (endpoint.method === 'put') return api.put<TResponse>(parts.path, body, config)\n return api.patch<TResponse>(parts.path, body, config)\n }\n const formData = pluginFormDataFromPayload(body)\n const config = {\n ...(queryConfig(parts.query) ?? {}),\n ...(options.signal ? { signal: options.signal } : {}),\n // Let Axios/browser set multipart boundaries.\n headers: { 'Content-Type': undefined },\n }\n\n if (endpoint.method === 'post') return api.post<TResponse>(parts.path, formData, config)\n if (endpoint.method === 'put') return api.put<TResponse>(parts.path, formData, config)\n return api.patch<TResponse>(parts.path, formData, config)\n}\n\n/** Fetch a generated plugin endpoint as a Blob and optionally trigger a browser download. */\nexport async function downloadEndpoint(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n payload?: unknown,\n options: DownloadEndpointOptions = {},\n): Promise<Blob> {\n if (endpoint.requestContentType === MULTIPART_FORM_DATA) {\n throw new Error('[MINT SDK] Plugin download endpoints do not support multipart request bodies')\n }\n\n const parts = pluginEndpointRequestParts(contract, endpoint, payload, options.baseUrl)\n const api = useApi({\n baseUrl: parts.baseUrl,\n withAuth: endpoint.requiresAuth !== false,\n })\n const config = {\n ...(endpointRequestConfig(endpoint, parts.query, { signal: options.signal }) ?? {}),\n responseType: 'blob' as const,\n }\n const rawBody = requestBodyFromPayload(payload, parts.requestPayload, endpoint)\n const body = rawBody === undefined ? undefined : endpointRequestBody(endpoint, rawBody)\n let blob: Blob\n\n if (endpoint.method === 'get') blob = await api.get<Blob>(parts.path, config)\n else if (endpoint.method === 'post') blob = await api.post<Blob>(parts.path, body, config)\n else if (endpoint.method === 'put') blob = await api.put<Blob>(parts.path, body, config)\n else if (endpoint.method === 'patch') blob = await api.patch<Blob>(parts.path, body, config)\n else if (endpoint.method === 'delete') {\n blob = await api.delete<Blob>(parts.path, body === undefined ? config : { ...config, data: body })\n } else {\n throw new Error(`[MINT SDK] Unsupported plugin endpoint method: ${endpoint.method}`)\n }\n\n if (options.filename && options.save !== false) {\n downloadBlob(blob, options.filename, {\n revokeObjectUrlDelayMs: options.revokeObjectUrlDelayMs,\n })\n }\n\n return blob\n}\n\nfunction normalizePluginSettingsOptions(options: UseSettingsEndpointOptions | string = {}): UseSettingsEndpointOptions {\n return typeof options === 'string' ? { pluginName: options } : options\n}\n\nconst PLATFORM_OWNED_SETTINGS_KEY = 'allowed_experiment_types'\n\n/**\n * Secret settings travel as `{ \"$secret\": \"<id>\" }` references, so an untouched\n * secret round-trips without ever exposing its value. Declaring the format on\n * every write keeps a `null` unambiguous: it clears the stored secret instead of\n * meaning \"unchanged\", which is what the deprecated placeholder form meant.\n */\nfunction withSecretFormat(\n payload: Record<string, unknown>,\n secretFormat: string | null,\n): Record<string, unknown> {\n if (!secretFormat) return payload\n return { ...payload, [pluginSecret.FORMAT_KEY]: secretFormat }\n}\n\nfunction responseSecretFormat(response: unknown): string | null {\n if (!response || typeof response !== 'object') return null\n const format = (response as Record<string, unknown>)[pluginSecret.FORMAT_KEY]\n return typeof format === 'string' && format ? format : null\n}\n\nfunction cloneRecord(value: Record<string, unknown>): Record<string, unknown> {\n return JSON.parse(JSON.stringify(value)) as Record<string, unknown>\n}\n\nfunction hasOwnSetting(settings: Record<string, unknown>, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(settings, key)\n}\n\nfunction responseSettingsPayload(response: unknown): Record<string, unknown> {\n if (!response || typeof response !== 'object') return {}\n const data = response as {\n config?: unknown\n settings?: unknown\n }\n if (data.config && typeof data.config === 'object') {\n return cloneRecord(data.config as Record<string, unknown>)\n }\n if (data.settings && typeof data.settings === 'object') {\n return cloneRecord(data.settings as Record<string, unknown>)\n }\n return {}\n}\n\nfunction responseSettingsRevision(response: unknown): string | null {\n if (!response || typeof response !== 'object') return null\n const revision = (response as { revision?: unknown }).revision\n return typeof revision === 'string' && revision ? revision : null\n}\n\nfunction topLevelSettingsPatch(\n current: Record<string, unknown>,\n keys: Iterable<string>,\n): Record<string, unknown> {\n return Object.fromEntries(\n [...keys].map(key => [\n key,\n hasOwnSetting(current, key) ? current[key] : null,\n ]),\n )\n}\n\nfunction changedTopLevelSettingKeys(\n current: Record<string, unknown>,\n saved: Record<string, unknown>,\n): Set<string> {\n return new Set(\n [...new Set([...Object.keys(current), ...Object.keys(saved)])].filter(\n key => JSON.stringify(current[key]) !== JSON.stringify(saved[key]),\n ),\n )\n}\n\nfunction platformOwnedSettingsPatch(settings: Record<string, unknown>): Record<string, unknown> {\n return {\n [PLATFORM_OWNED_SETTINGS_KEY]: hasOwnSetting(settings, PLATFORM_OWNED_SETTINGS_KEY)\n ? settings[PLATFORM_OWNED_SETTINGS_KEY]\n : null,\n }\n}\n\n/** Load, edit, and persist plugin settings from platform config or the plugin-local settings router. */\nexport function useSettingsEndpoint<TSettings = Record<string, unknown>>(\n options: UseSettingsEndpointOptions | string = {},\n): UseSettingsEndpointReturn<TSettings> {\n const resolvedOptions = normalizePluginSettingsOptions(options)\n const injectedContext = getInjectedPlatformContext()\n const { plugin, integrated: isIntegrated } = usePlatform()\n const platformApi = useApi({ baseUrl: resolvedOptions.platformApiBaseUrl ?? injectedContext?.platformApiUrl })\n const pluginApi = useApi({ baseUrl: resolvedOptions.apiBaseUrl ?? injectedContext?.plugin?.api_prefix })\n\n const values = ref({}) as Ref<PluginSettingsValues<TSettings>>\n const savedValues = shallowRef<Record<string, unknown>>({})\n const revision = ref<string | null>(null)\n const secretFormat = ref<string | null>(null)\n const request = useRequest('Plugin settings request failed.')\n const isLoading = ref(false)\n const isSaving = ref(false)\n\n const resolvedPluginName = computed(() =>\n resolvedOptions.pluginName ?? plugin.value?.name ?? injectedContext?.plugin?.name ?? '',\n )\n const usesPluginSettingsApi = computed(() => {\n const currentPlugin = injectedContext?.plugin ?? plugin.value\n return currentPlugin?.settings_api === 'plugin'\n && resolvedPluginName.value === currentPlugin.name\n })\n const usesPlatformConfig = computed(() =>\n Boolean(\n (isIntegrated.value || injectedContext?.isIntegrated)\n && resolvedPluginName.value\n && !usesPluginSettingsApi.value,\n ),\n )\n const settings = computed(() => values.value)\n const isDirty = computed(() => JSON.stringify(values.value) !== JSON.stringify(savedValues.value))\n const settingsConfig = computed<TopBarSettingsConfig>(() => ({\n title: resolvedOptions.title,\n spec: resolvedOptions.schema ?? undefined,\n showAppearance: resolvedOptions.showAppearance,\n values: values.value,\n }))\n\n function setValues(nextValues: Record<string, unknown>): void {\n values.value = cloneRecord(nextValues) as PluginSettingsValues<TSettings>\n }\n\n function applyLoaded(\n nextValues: Record<string, unknown>,\n nextRevision: string | null,\n nextSecretFormat: string | null = secretFormat.value,\n ): void {\n const cloned = cloneRecord(nextValues)\n values.value = cloned as PluginSettingsValues<TSettings>\n savedValues.value = cloneRecord(cloned)\n revision.value = nextRevision\n secretFormat.value = nextSecretFormat\n }\n\n async function load(): Promise<void> {\n isLoading.value = true\n try {\n const response = usesPlatformConfig.value\n ? await request.run(\n () => platformApi.get<{ plugin_name: string; config: Record<string, unknown> }>(\n `/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,\n ),\n { kind: 'load', errorMessage: 'Failed to load plugin settings' },\n )\n : await request.run(\n () => pluginApi.get<{ settings: Record<string, unknown>; mode?: string }>('/settings'),\n { kind: 'load', errorMessage: 'Failed to load plugin settings' },\n )\n applyLoaded(\n responseSettingsPayload(response),\n responseSettingsRevision(response),\n responseSecretFormat(response),\n )\n } catch {\n // Error state is handled by request.run().\n } finally {\n isLoading.value = false\n }\n }\n\n async function save(nextValues?: Record<string, unknown>): Promise<boolean> {\n if (nextValues) {\n setValues(nextValues)\n }\n\n isSaving.value = true\n try {\n const payload = cloneRecord(values.value)\n let response: unknown\n if (usesPlatformConfig.value) {\n const dirtyKeys = changedTopLevelSettingKeys(payload, savedValues.value)\n const platformOwnedChanged = dirtyKeys.has(PLATFORM_OWNED_SETTINGS_KEY)\n const typedKeys = [...dirtyKeys].filter(\n key => key !== PLATFORM_OWNED_SETTINGS_KEY,\n )\n let nextRevision = revision.value\n\n if (typedKeys.length) {\n response = await request.run(\n () => platformApi.patch<{\n plugin_name: string\n config: Record<string, unknown>\n revision?: string\n }>(\n `/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,\n {\n config: withSecretFormat(\n topLevelSettingsPatch(payload, typedKeys),\n secretFormat.value,\n ),\n },\n ),\n { kind: 'save', errorMessage: 'Failed to save plugin settings' },\n )\n nextRevision = responseSettingsRevision(response) ?? nextRevision\n }\n\n if (platformOwnedChanged) {\n response = await request.run(\n () => platformApi.patch<{\n plugin_name: string\n config: Record<string, unknown>\n revision?: string\n }>(\n `/plugins/${encodeURIComponent(resolvedPluginName.value)}/config`,\n { config: platformOwnedSettingsPatch(payload) },\n ),\n { kind: 'save', errorMessage: 'Failed to save plugin settings' },\n )\n nextRevision = responseSettingsRevision(response) ?? nextRevision\n }\n\n if (!response) return true\n applyLoaded(\n responseSettingsPayload(response),\n nextRevision,\n responseSecretFormat(response) ?? secretFormat.value,\n )\n return true\n } else {\n const ifMatch = revision.value\n ? { headers: { 'If-Match': `\"${revision.value}\"` } }\n : undefined\n response = await request.run(\n () => pluginApi.put<{\n settings: Record<string, unknown>\n mode?: string\n revision?: string\n }>('/settings', withSecretFormat(payload, secretFormat.value), ifMatch),\n { kind: 'save', errorMessage: 'Failed to save plugin settings' },\n )\n }\n applyLoaded(\n responseSettingsPayload(response),\n responseSettingsRevision(response) ?? revision.value,\n responseSecretFormat(response) ?? secretFormat.value,\n )\n return true\n } catch {\n return false\n } finally {\n isSaving.value = false\n }\n }\n\n function reset(): void {\n values.value = cloneRecord(savedValues.value) as PluginSettingsValues<TSettings>\n request.clearError()\n }\n\n onMounted(() => {\n if (resolvedOptions.loadOnMount !== false) {\n void load()\n }\n })\n\n return {\n settings,\n values,\n config: values,\n settingsConfig,\n isLoading,\n isSaving,\n error: request.error,\n lastLoadedAt: request.lastLoadedAt,\n lastSavedAt: request.lastSavedAt,\n isDirty,\n setValues,\n load,\n save,\n reset,\n }\n}\n\nfunction streamRequestAuth(\n requestHeaders: HeadersInit | undefined,\n policy: { requiresAuth: boolean },\n): {\n headers: Headers\n token?: string\n} {\n const headers = new Headers(requestHeaders)\n const authStore = useAuthStore()\n if (!authStore.isInitialized) {\n authStore.initialize()\n }\n if (policy.requiresAuth && authStore.token && !headers.has('Authorization')) {\n headers.set('Authorization', `Bearer ${authStore.token}`)\n }\n const token = policy.requiresAuth ? authStore.token : undefined\n return {\n headers,\n token: token && headers.get('Authorization') === `Bearer ${token}` ? token : undefined,\n }\n}\n\nfunction pluginEventStreamTransport(\n endpoint: PluginEndpointDefinition,\n): EventStreamTransport {\n return async (url, init = {}) => {\n if (typeof fetch !== 'function') {\n throw new Error('[MINT SDK] fetch is required for plugin event streams.')\n }\n const requestAuth = streamRequestAuth(\n init.headers,\n { requiresAuth: endpoint.requiresAuth !== false },\n )\n const response = await fetch(url, { ...init, headers: requestAuth.headers })\n if (response.status === 401) {\n throw authenticationRequiredError(response, requestAuth.token)\n }\n return response\n }\n}\n\nfunction looksLikeEventStreamOptions(value: unknown): value is StreamOptions {\n if (!value || typeof value !== 'object') return false\n const candidate = value as Record<string, unknown>\n return [\n 'baseUrl',\n 'immediate',\n 'reconnect',\n 'reconnectDelayMs',\n 'parseJson',\n 'credentials',\n 'headers',\n 'onOpen',\n 'onMessage',\n 'onError',\n ].some(key => key in candidate)\n}\n\n/** Consume a generated plugin SSE endpoint with auth headers, reconnect, and teardown. */\nexport function useEventStream<TData = string>(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n options?: StreamOptions<TData>,\n): UseEventStreamReturn<TData>\nexport function useEventStream<TData = string>(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n payload?: unknown,\n options?: StreamOptions<TData>,\n): UseEventStreamReturn<TData>\nexport function useEventStream<TData = string>(\n contract: PluginContract,\n endpoint: PluginEndpointDefinition,\n payloadOrOptions?: unknown,\n maybeOptions?: StreamOptions<TData>,\n): UseEventStreamReturn<TData> {\n if (endpoint.method !== 'get') {\n throw new Error(`[MINT SDK] Plugin event streams must use GET; got ${endpoint.method}`)\n }\n const payload = maybeOptions === undefined && looksLikeEventStreamOptions(payloadOrOptions)\n ? undefined\n : payloadOrOptions\n const options = (\n maybeOptions === undefined && looksLikeEventStreamOptions(payloadOrOptions)\n ? payloadOrOptions\n : maybeOptions ?? {}\n ) as StreamOptions<TData>\n const responseType = endpoint.responseType?.trim()\n const parseJson = options.parseJson ?? Boolean(responseType && responseType !== 'string')\n return createEventStream<TData>(\n () => buildEndpointUrl(contract, endpoint, payload, { baseUrl: options.baseUrl }),\n {\n transport: pluginEventStreamTransport(endpoint),\n immediate: options.immediate,\n reconnect: options.reconnect,\n reconnectDelayMs: options.reconnectDelayMs,\n parseJson,\n credentials: options.credentials,\n headers: options.headers,\n shouldReconnect: cause => !(cause instanceof AuthenticationRequiredError),\n onOpen: options.onOpen,\n onMessage: options.onMessage,\n onError: options.onError,\n errorLabel: 'Plugin event stream',\n },\n )\n}\n","<script setup lang=\"ts\">\nimport type * as PlotlyType from 'plotly.js-dist-min'\nimport { computed, toRaw } from 'vue'\n\nimport PlotlyChart from './PlotlyChart.vue'\n\nconst props = defineProps<{\n spec: Record<string, unknown>\n fallback?: { media_type: string; data: string }\n}>()\n\nconst data = computed<PlotlyType.Data[]>(() => {\n const traces = toRaw(props.spec).data\n return Array.isArray(traces) ? [...toRaw(traces)] as PlotlyType.Data[] : []\n})\nconst layout = computed<Partial<PlotlyType.Layout>>(() => (\n props.spec.layout && typeof props.spec.layout === 'object'\n ? props.spec.layout as Partial<PlotlyType.Layout>\n : {}\n))\nconst config = computed<Partial<PlotlyType.Config>>(() => (\n props.spec.config && typeof props.spec.config === 'object'\n ? props.spec.config as Partial<PlotlyType.Config>\n : {}\n))\n</script>\n\n<template>\n <PlotlyChart\n title=\"Interactive chart\"\n :data=\"data\"\n :layout=\"layout\"\n :config=\"config\"\n >\n <template v-if=\"fallback\" #error>\n <img\n class=\"mint-generated-result__image\"\n :src=\"`data:${fallback.media_type};base64,${fallback.data}`\"\n alt=\"Static Plotly chart fallback\"\n >\n </template>\n </PlotlyChart>\n</template>\n\n<style scoped>\n.mint-generated-result__image {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 0 auto;\n}\n</style>\n","<script setup lang=\"ts\">\nimport type * as PlotlyType from 'plotly.js-dist-min'\nimport { computed, toRaw } from 'vue'\n\nimport PlotlyChart from './PlotlyChart.vue'\n\nconst props = defineProps<{\n spec: Record<string, unknown>\n fallback?: { media_type: string; data: string }\n}>()\n\nconst data = computed<PlotlyType.Data[]>(() => {\n const traces = toRaw(props.spec).data\n return Array.isArray(traces) ? [...toRaw(traces)] as PlotlyType.Data[] : []\n})\nconst layout = computed<Partial<PlotlyType.Layout>>(() => (\n props.spec.layout && typeof props.spec.layout === 'object'\n ? props.spec.layout as Partial<PlotlyType.Layout>\n : {}\n))\nconst config = computed<Partial<PlotlyType.Config>>(() => (\n props.spec.config && typeof props.spec.config === 'object'\n ? props.spec.config as Partial<PlotlyType.Config>\n : {}\n))\n</script>\n\n<template>\n <PlotlyChart\n title=\"Interactive chart\"\n :data=\"data\"\n :layout=\"layout\"\n :config=\"config\"\n >\n <template v-if=\"fallback\" #error>\n <img\n class=\"mint-generated-result__image\"\n :src=\"`data:${fallback.media_type};base64,${fallback.data}`\"\n alt=\"Static Plotly chart fallback\"\n >\n </template>\n </PlotlyChart>\n</template>\n\n<style scoped>\n.mint-generated-result__image {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 0 auto;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * Render standardized text, table, image, Plotly, JSON, and artifact job results.\n *\n * @example Recommended\n * ```vue\n * <GeneratedResultRenderer :result=\"result\" />\n * ```\n */\nimport { computed } from 'vue'\n\nimport { mintApiErrorFromResponse } from '../apiErrors'\nimport { createGeneratedAnalysisTransport } from '../composables/useJobsEndpoint'\nimport { downloadBlob } from '../composables/createClient'\nimport { defineDataFrame } from '../composables/componentSpecs'\nimport type { GeneratedAnalysisResult } from '../types/generated-ui'\nimport ChartContainer from './ChartContainer.vue'\nimport DataFrame from './DataFrame.vue'\nimport GeneratedPlotlyResult from './GeneratedPlotlyResult.vue'\n\ninterface Props {\n result: GeneratedAnalysisResult\n /** Show the search box above a table result. */\n searchable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), { searchable: true })\n\nconst table = computed(() => defineDataFrame({\n columns: props.result.kind === 'table'\n ? props.result.columns.map(key => ({ key, label: humanize(key), sortable: true }))\n : [],\n sortable: true,\n searchable: props.searchable,\n stickyHeader: true,\n maxHeight: '32rem',\n}))\n\nconst formattedJson = computed(() => (\n props.result.kind === 'json'\n ? JSON.stringify(props.result.value, null, 2)\n : ''\n))\n\nconst artifactHref = computed(() => {\n if (props.result.kind !== 'artifact') return ''\n if (props.result.download_url) return props.result.download_url\n return props.result.data\n ? `data:${props.result.media_type};base64,${props.result.data}`\n : ''\n})\n\n// A plugin route needs a Bearer credential, which a plain link navigation never sends.\n// ponytail: buffers the whole file in memory; stream to showSaveFilePicker() if large files strain the tab.\nasync function downloadManagedArtifact(event: MouseEvent): Promise<void> {\n if (props.result.kind !== 'artifact' || !props.result.download_url) return\n event.preventDefault()\n const response = await createGeneratedAnalysisTransport()(props.result.download_url)\n if (!response.ok) {\n const payload: unknown = await response.json().catch(() => null)\n throw mintApiErrorFromResponse(response.status, payload, {\n requestId: response.headers.get('X-Request-ID'),\n })\n }\n downloadBlob(await response.blob(), props.result.name)\n}\n\nfunction humanize(value: string): string {\n return value.replace(/[_-]+/g, ' ').replace(/^\\w/, character => character.toUpperCase())\n}\n</script>\n\n<template>\n <article class=\"mint-generated-result\" aria-live=\"polite\">\n <p v-if=\"result.kind === 'text'\" class=\"mint-generated-result__text\">\n {{ result.text }}\n </p>\n\n <pre v-else-if=\"result.kind === 'json'\" class=\"mint-generated-result__json\">{{ formattedJson }}</pre>\n\n <DataFrame\n v-else-if=\"result.kind === 'table'\"\n :spec=\"table\"\n :data=\"result.rows\"\n />\n\n <ChartContainer v-else-if=\"result.kind === 'image'\" title=\"Analysis chart\">\n <img\n class=\"mint-generated-result__image\"\n :src=\"`data:${result.media_type};base64,${result.data}`\"\n :alt=\"result.alt\"\n >\n </ChartContainer>\n\n <GeneratedPlotlyResult\n v-else-if=\"result.kind === 'plotly'\"\n :spec=\"result.spec\"\n :fallback=\"result.fallback\"\n />\n\n <a\n v-else\n class=\"mint-generated-result__artifact\"\n :href=\"artifactHref\"\n :download=\"result.name\"\n @click=\"downloadManagedArtifact\"\n >\n <span>{{ result.name }}</span>\n <small>{{ result.media_type }}</small>\n </a>\n </article>\n</template>\n\n<style scoped>\n.mint-generated-result {\n width: 100%;\n}\n\n.mint-generated-result__text,\n.mint-generated-result__json {\n margin: 0;\n color: var(--text-primary);\n}\n\n.mint-generated-result__text {\n max-width: 72ch;\n white-space: pre-wrap;\n line-height: 1.65;\n}\n\n.mint-generated-result__json {\n overflow: auto;\n padding: 0.75rem 1rem;\n border-radius: var(--radius-md);\n background: var(--bg-tertiary);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.55;\n white-space: pre-wrap;\n}\n\n.mint-generated-result__image {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 0 auto;\n}\n\n.mint-generated-result__artifact {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border-color);\n border-radius: var(--radius-lg);\n color: var(--color-primary);\n text-decoration: none;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * Render standardized text, table, image, Plotly, JSON, and artifact job results.\n *\n * @example Recommended\n * ```vue\n * <GeneratedResultRenderer :result=\"result\" />\n * ```\n */\nimport { computed } from 'vue'\n\nimport { mintApiErrorFromResponse } from '../apiErrors'\nimport { createGeneratedAnalysisTransport } from '../composables/useJobsEndpoint'\nimport { downloadBlob } from '../composables/createClient'\nimport { defineDataFrame } from '../composables/componentSpecs'\nimport type { GeneratedAnalysisResult } from '../types/generated-ui'\nimport ChartContainer from './ChartContainer.vue'\nimport DataFrame from './DataFrame.vue'\nimport GeneratedPlotlyResult from './GeneratedPlotlyResult.vue'\n\ninterface Props {\n result: GeneratedAnalysisResult\n /** Show the search box above a table result. */\n searchable?: boolean\n}\n\nconst props = withDefaults(defineProps<Props>(), { searchable: true })\n\nconst table = computed(() => defineDataFrame({\n columns: props.result.kind === 'table'\n ? props.result.columns.map(key => ({ key, label: humanize(key), sortable: true }))\n : [],\n sortable: true,\n searchable: props.searchable,\n stickyHeader: true,\n maxHeight: '32rem',\n}))\n\nconst formattedJson = computed(() => (\n props.result.kind === 'json'\n ? JSON.stringify(props.result.value, null, 2)\n : ''\n))\n\nconst artifactHref = computed(() => {\n if (props.result.kind !== 'artifact') return ''\n if (props.result.download_url) return props.result.download_url\n return props.result.data\n ? `data:${props.result.media_type};base64,${props.result.data}`\n : ''\n})\n\n// A plugin route needs a Bearer credential, which a plain link navigation never sends.\n// ponytail: buffers the whole file in memory; stream to showSaveFilePicker() if large files strain the tab.\nasync function downloadManagedArtifact(event: MouseEvent): Promise<void> {\n if (props.result.kind !== 'artifact' || !props.result.download_url) return\n event.preventDefault()\n const response = await createGeneratedAnalysisTransport()(props.result.download_url)\n if (!response.ok) {\n const payload: unknown = await response.json().catch(() => null)\n throw mintApiErrorFromResponse(response.status, payload, {\n requestId: response.headers.get('X-Request-ID'),\n })\n }\n downloadBlob(await response.blob(), props.result.name)\n}\n\nfunction humanize(value: string): string {\n return value.replace(/[_-]+/g, ' ').replace(/^\\w/, character => character.toUpperCase())\n}\n</script>\n\n<template>\n <article class=\"mint-generated-result\" aria-live=\"polite\">\n <p v-if=\"result.kind === 'text'\" class=\"mint-generated-result__text\">\n {{ result.text }}\n </p>\n\n <pre v-else-if=\"result.kind === 'json'\" class=\"mint-generated-result__json\">{{ formattedJson }}</pre>\n\n <DataFrame\n v-else-if=\"result.kind === 'table'\"\n :spec=\"table\"\n :data=\"result.rows\"\n />\n\n <ChartContainer v-else-if=\"result.kind === 'image'\" title=\"Analysis chart\">\n <img\n class=\"mint-generated-result__image\"\n :src=\"`data:${result.media_type};base64,${result.data}`\"\n :alt=\"result.alt\"\n >\n </ChartContainer>\n\n <GeneratedPlotlyResult\n v-else-if=\"result.kind === 'plotly'\"\n :spec=\"result.spec\"\n :fallback=\"result.fallback\"\n />\n\n <a\n v-else\n class=\"mint-generated-result__artifact\"\n :href=\"artifactHref\"\n :download=\"result.name\"\n @click=\"downloadManagedArtifact\"\n >\n <span>{{ result.name }}</span>\n <small>{{ result.media_type }}</small>\n </a>\n </article>\n</template>\n\n<style scoped>\n.mint-generated-result {\n width: 100%;\n}\n\n.mint-generated-result__text,\n.mint-generated-result__json {\n margin: 0;\n color: var(--text-primary);\n}\n\n.mint-generated-result__text {\n max-width: 72ch;\n white-space: pre-wrap;\n line-height: 1.65;\n}\n\n.mint-generated-result__json {\n overflow: auto;\n padding: 0.75rem 1rem;\n border-radius: var(--radius-md);\n background: var(--bg-tertiary);\n font-family: var(--font-mono);\n font-size: 0.8125rem;\n line-height: 1.55;\n white-space: pre-wrap;\n}\n\n.mint-generated-result__image {\n display: block;\n max-width: 100%;\n height: auto;\n margin: 0 auto;\n}\n\n.mint-generated-result__artifact {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 1rem;\n padding: 1rem;\n border: 1px solid var(--border-color);\n border-radius: var(--radius-lg);\n color: var(--color-primary);\n text-decoration: none;\n}\n</style>\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 /**\n * Keyboard alternative to the pointer drag: shifts an event by one slot\n * (`mode: 'move'`) or grows/shrinks its end by one slot (`mode: 'resize'`),\n * staying inside the visible day. Returns whether the nudge was applied.\n */\n function nudgeEvent(\n event: ScheduleEvent,\n direction: -1 | 1,\n mode: 'move' | 'resize' = 'move',\n ): boolean {\n if (options.readonly.value) return false\n if (mode === 'move' && event.draggable === false) return false\n if (mode === 'resize' && event.resizable === false) return false\n\n const slotMin = options.slotDuration.value\n const dayStart = options.dayStartHour.value * 60\n const dayEnd = options.dayEndHour.value * 60\n const start = dateToMinutes(new Date(event.start))\n const end = dateToMinutes(new Date(event.end))\n\n const nextStart = mode === 'move' ? start + direction * slotMin : start\n const nextEnd = mode === 'move'\n ? end + direction * slotMin\n : Math.max(start + slotMin, end + direction * slotMin)\n\n if (nextStart < dayStart || nextEnd > dayEnd || nextEnd <= nextStart) return false\n\n const base = new Date(event.start)\n const newStart = minutesToDate(base, nextStart)\n const newEnd = minutesToDate(base, nextEnd)\n\n if (mode === 'move') options.onMoveComplete?.(event, newStart, newEnd)\n else options.onResizeComplete?.(event, newStart, newEnd)\n return true\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 nudgeEvent,\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","import { computed, type Ref } from 'vue'\nimport type {\n ScheduleView,\n ScheduleEvent,\n ScheduleBlockedSlot,\n ScheduleEventStatus,\n} from '../types/components'\nimport { formatTime } from '../utils/time'\n\ntype ReadableRef<T> = Readonly<Ref<T>>\n\nexport const SCHEDULE_SLOT_HEIGHT = 40\n\nexport interface UseScheduleCalendarLayoutOptions {\n currentView: ReadableRef<ScheduleView>\n currentDate: ReadableRef<Date>\n now: ReadableRef<Date>\n events: ReadableRef<readonly ScheduleEvent[]>\n blockedSlots: ReadableRef<readonly ScheduleBlockedSlot[]>\n dayStartHour: ReadableRef<number>\n dayEndHour: ReadableRef<number>\n slotDuration: ReadableRef<number>\n weekStartsOn: ReadableRef<0 | 1>\n locale: ReadableRef<string>\n showNowIndicator: ReadableRef<boolean>\n slotHeight?: ReadableRef<number>\n}\n\n/** Builds ScheduleCalendar date ranges, event layout styles, and display labels. */\nexport function useScheduleCalendarLayout(options: UseScheduleCalendarLayoutOptions) {\n const slotHeight = computed(() => options.slotHeight?.value ?? SCHEDULE_SLOT_HEIGHT)\n const totalSlots = computed(() => {\n return ((options.dayEndHour.value - options.dayStartHour.value) * 60) / options.slotDuration.value\n })\n\n const timeLabels = computed(() => buildTimeLabels(\n options.dayStartHour.value,\n options.slotDuration.value,\n totalSlots.value,\n ))\n const weekDays = computed(() => buildWeekDays(options.currentDate.value, options.weekStartsOn.value))\n const visibleDays = computed(() => {\n if (options.currentView.value === 'day') return [options.currentDate.value]\n if (options.currentView.value === 'week') return weekDays.value\n return []\n })\n const headerTitle = computed(() => buildHeaderTitle(\n options.currentView.value,\n options.currentDate.value,\n weekDays.value,\n options.locale.value,\n ))\n const monthDays = computed(() => buildMonthDays(options.currentDate.value, options.weekStartsOn.value))\n const monthWeekdayLabels = computed(() => buildMonthWeekdayLabels(\n options.locale.value,\n options.weekStartsOn.value,\n ))\n\n function isToday(date: Date): boolean {\n return isSameDay(date, options.now.value)\n }\n\n function getEventStyle(event: ScheduleEvent, dayDate: Date): Record<string, string> | null {\n return getTimedItemStyle(\n event.start,\n event.end,\n dayDate,\n options.dayStartHour.value,\n options.dayEndHour.value,\n options.slotDuration.value,\n slotHeight.value,\n true,\n slotHeight.value / 2,\n )\n }\n\n function getBlockedStyle(slot: ScheduleBlockedSlot): Record<string, string> {\n return getTimedItemStyle(\n slot.start,\n slot.end,\n new Date(slot.start),\n options.dayStartHour.value,\n options.dayEndHour.value,\n options.slotDuration.value,\n slotHeight.value,\n false,\n 0,\n ) ?? { top: '0px', height: '0px' }\n }\n\n function getNowIndicatorStyle(): Record<string, string> | null {\n if (!options.showNowIndicator.value) return null\n const nowMin = dateToMinutes(options.now.value)\n const dayStartMin = options.dayStartHour.value * 60\n const dayEndMin = options.dayEndHour.value * 60\n if (nowMin < dayStartMin || nowMin > dayEndMin) return null\n\n const pixelsPerMin = slotHeight.value / options.slotDuration.value\n return { top: `${(nowMin - dayStartMin) * pixelsPerMin}px` }\n }\n\n function getEventsForDay(day: Date): ScheduleEvent[] {\n return eventsForDate(options.events.value, day)\n }\n\n function getEventsForDate(date: Date): ScheduleEvent[] {\n return eventsForDate(options.events.value, date)\n }\n\n function getBlockedForDay(day: Date): ScheduleBlockedSlot[] {\n return options.blockedSlots.value.filter((slot) => isSameDay(new Date(slot.start), day))\n }\n\n return {\n totalSlots,\n timeLabels,\n weekDays,\n visibleDays,\n headerTitle,\n monthDays,\n monthWeekdayLabels,\n isToday,\n isSameDay,\n getEventStyle,\n getEventsForDay,\n getEventsForDate,\n getBlockedForDay,\n getBlockedStyle,\n getNowIndicatorStyle,\n getStatusClass,\n getMonthStatusClass,\n formatEventTime,\n }\n}\n\nexport function parseScheduleModelDate(value?: Date | string): Date {\n if (!value) return new Date()\n if (value instanceof Date) return value\n const d = new Date(value)\n return isNaN(d.getTime()) ? new Date() : d\n}\n\nexport function getWeekStart(date: Date, weekStartsOn: 0 | 1): Date {\n const d = new Date(date)\n const day = d.getDay()\n const diff = (day - weekStartsOn + 7) % 7\n d.setDate(d.getDate() - diff)\n return d\n}\n\nexport function isSameDay(a: Date, b: Date): boolean {\n return a.toDateString() === b.toDateString()\n}\n\nfunction buildTimeLabels(dayStartHour: number, slotDuration: number, totalSlots: number): string[] {\n const labels: string[] = []\n for (let i = 0; i <= totalSlots; i++) {\n const totalMinutes = dayStartHour * 60 + i * slotDuration\n const hour = Math.floor(totalMinutes / 60)\n const minute = totalMinutes % 60\n labels.push(formatTime(hour, minute, '24h'))\n }\n return labels\n}\n\nfunction buildWeekDays(currentDate: Date, weekStartsOn: 0 | 1): Date[] {\n const days: Date[] = []\n const start = getWeekStart(currentDate, weekStartsOn)\n for (let i = 0; i < 7; i++) {\n const d = new Date(start)\n d.setDate(d.getDate() + i)\n days.push(d)\n }\n return days\n}\n\nfunction buildHeaderTitle(\n view: ScheduleView,\n currentDate: Date,\n weekDays: Date[],\n locale: string,\n): string {\n if (view === 'day') {\n return currentDate.toLocaleDateString(locale, {\n weekday: 'long',\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n })\n }\n if (view === 'week') {\n const first = weekDays[0]\n const last = weekDays[6]\n const sameMonth = first.getMonth() === last.getMonth()\n if (sameMonth) {\n return `${first.toLocaleDateString(locale, { month: 'short', day: 'numeric' })} - ${last.getDate()}, ${last.getFullYear()}`\n }\n return `${first.toLocaleDateString(locale, { month: 'short', day: 'numeric' })} - ${last.toLocaleDateString(locale, { month: 'short', day: 'numeric' })}, ${last.getFullYear()}`\n }\n return currentDate.toLocaleDateString(locale, { year: 'numeric', month: 'long' })\n}\n\nfunction buildMonthDays(currentDate: Date, weekStartsOn: 0 | 1): { date: Date; isCurrentMonth: boolean }[] {\n const year = currentDate.getFullYear()\n const month = currentDate.getMonth()\n const firstDay = new Date(year, month, 1)\n const lastDay = new Date(year, month + 1, 0)\n const days: { date: Date; isCurrentMonth: boolean }[] = []\n\n let startPadding = firstDay.getDay() - weekStartsOn\n if (startPadding < 0) startPadding += 7\n\n for (let i = startPadding - 1; i >= 0; i--) {\n days.push({ date: new Date(year, month, -i), isCurrentMonth: false })\n }\n\n for (let i = 1; i <= lastDay.getDate(); i++) {\n days.push({ date: new Date(year, month, i), isCurrentMonth: true })\n }\n\n const remaining = 42 - days.length\n for (let i = 1; i <= remaining; i++) {\n days.push({ date: new Date(year, month + 1, i), isCurrentMonth: false })\n }\n\n return days\n}\n\nfunction buildMonthWeekdayLabels(locale: string, weekStartsOn: 0 | 1): string[] {\n const labels: string[] = []\n const base = new Date(2024, 0, weekStartsOn === 1 ? 1 : 7)\n for (let i = 0; i < 7; i++) {\n const d = new Date(base)\n d.setDate(d.getDate() + i)\n labels.push(d.toLocaleDateString(locale, { weekday: 'short' }))\n }\n return labels\n}\n\nfunction getTimedItemStyle(\n startValue: string,\n endValue: string,\n dayDate: Date,\n dayStartHour: number,\n dayEndHour: number,\n slotDuration: number,\n slotHeight: number,\n requireSameDayEdge: boolean,\n minimumHeight: number,\n): Record<string, string> | null {\n const start = new Date(startValue)\n const end = new Date(endValue)\n if (requireSameDayEdge && !isSameDay(start, dayDate) && !isSameDay(end, dayDate)) return null\n\n const dayStartMin = dayStartHour * 60\n const startMin = Math.max(dateToMinutes(start), dayStartMin) - dayStartMin\n const endMin = Math.min(dateToMinutes(end), dayEndHour * 60) - dayStartMin\n const pixelsPerMin = slotHeight / slotDuration\n return {\n top: `${startMin * pixelsPerMin}px`,\n height: `${Math.max((endMin - startMin) * pixelsPerMin, minimumHeight)}px`,\n }\n}\n\nfunction eventsForDate(events: readonly ScheduleEvent[], date: Date): ScheduleEvent[] {\n return events.filter((event) => isSameDay(new Date(event.start), date))\n}\n\nfunction dateToMinutes(date: Date): number {\n return date.getHours() * 60 + date.getMinutes()\n}\n\nfunction getStatusClass(status?: ScheduleEventStatus): string {\n if (!status) return 'mint-schedule__event--confirmed'\n return `mint-schedule__event--${status}`\n}\n\nfunction getMonthStatusClass(status?: ScheduleEventStatus): string {\n if (!status) return 'mint-schedule__month-event--confirmed'\n return `mint-schedule__month-event--${status}`\n}\n\nfunction formatEventTime(event: ScheduleEvent): string {\n const start = new Date(event.start)\n const end = new Date(event.end)\n return `${formatTime(start.getHours(), start.getMinutes())} - ${formatTime(end.getHours(), end.getMinutes())}`\n}\n","<script setup lang=\"ts\">\n/** Drag-and-drop week/day calendar for scheduling lab events in configurable time slots. */\nimport { ref, computed, watch, onMounted, onUnmounted } from 'vue'\nimport type {\n ScheduleView,\n ScheduleEvent,\n ScheduleBlockedSlot,\n} from '../types/components'\nimport { useScheduleDrag } from '../composables/useScheduleDrag'\nimport { useReorderAnnouncer } from '../composables/useListReorder'\nimport {\n SCHEDULE_SLOT_HEIGHT,\n parseScheduleModelDate,\n useScheduleCalendarLayout,\n} from '../composables/useScheduleCalendarLayout'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\ninterface Props {\n modelValue?: Date | string\n view?: ScheduleView\n events?: ScheduleEvent[]\n dayStartHour?: number\n dayEndHour?: number\n slotDuration?: number\n showNowIndicator?: boolean\n readonly?: boolean\n blockedSlots?: ScheduleBlockedSlot[]\n weekStartsOn?: 0 | 1\n showViewToggle?: boolean\n showNavigation?: boolean\n statusColors?: Record<string, string>\n locale?: string\n /** Replace the calendar grid with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the calendar grid is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /**\n * Opt-in empty-state headline. An empty grid is meaningful on a calendar, so\n * the empty state only replaces it when this message is supplied and there\n * are no events.\n */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n view: 'week',\n events: () => [],\n dayStartHour: 6,\n dayEndHour: 22,\n slotDuration: 30,\n showNowIndicator: true,\n readonly: false,\n blockedSlots: () => [],\n weekStartsOn: 1,\n showViewToggle: true,\n showNavigation: true,\n locale: 'en-US',\n loading: false,\n error: null,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Date]\n 'update:view': [value: ScheduleView]\n 'event-click': [event: ScheduleEvent]\n 'event-create': [context: { start: Date; end: Date }]\n 'event-update': [context: { event: ScheduleEvent; newStart: Date; newEnd: Date }]\n 'slot-click': [context: { date: Date; hour: number; minute: number }]\n 'navigate': [context: { direction: string; date: Date }]\n}>()\n\nconst SLOT_HEIGHT = SCHEDULE_SLOT_HEIGHT\nconst currentView = ref<ScheduleView>(props.view)\n\n/** Empty state is opt-in: it needs an `emptyText` and an event-free range. */\nconst showEmptyState = computed(() =>\n Boolean(props.emptyText) && props.events.length === 0,\n)\nconst currentDate = ref<Date>(parseScheduleModelDate(props.modelValue))\nconst nowTimer = ref<ReturnType<typeof setInterval>>()\nconst now = ref(new Date())\nconst bodyRef = ref<HTMLDivElement>()\n\nwatch(() => props.view, (v) => { currentView.value = v })\nwatch(() => props.modelValue, (v) => { if (v) currentDate.value = parseScheduleModelDate(v) })\n\nconst slotHeightRef = computed(() => SLOT_HEIGHT)\nconst slotDurationRef = computed(() => props.slotDuration)\nconst dayStartRef = computed(() => props.dayStartHour)\nconst dayEndRef = computed(() => props.dayEndHour)\nconst readonlyRef = computed(() => props.readonly)\nconst eventsRef = computed(() => props.events)\nconst blockedSlotsRef = computed(() => props.blockedSlots)\nconst weekStartsOnRef = computed(() => props.weekStartsOn)\nconst localeRef = computed(() => props.locale)\nconst showNowIndicatorRef = computed(() => props.showNowIndicator)\n\nconst { announcement, announce, announcerStyle } = useReorderAnnouncer()\n\nconst { isDragging, ghost, startCreate, startMove, startResize, nudgeEvent } = useScheduleDrag({\n slotDuration: slotDurationRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n readonly: readonlyRef,\n slotHeight: slotHeightRef,\n onCreateComplete(start, end) {\n emit('event-create', { start, end })\n },\n onMoveComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n onResizeComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n})\n\nconst {\n totalSlots,\n timeLabels,\n visibleDays,\n headerTitle,\n monthDays,\n monthWeekdayLabels,\n isToday,\n isSameDay,\n getEventStyle,\n getEventsForDay,\n getEventsForDate,\n getBlockedForDay,\n getBlockedStyle,\n getNowIndicatorStyle,\n getStatusClass,\n getMonthStatusClass,\n formatEventTime,\n} = useScheduleCalendarLayout({\n currentView,\n currentDate,\n now,\n events: eventsRef,\n blockedSlots: blockedSlotsRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n slotDuration: slotDurationRef,\n weekStartsOn: weekStartsOnRef,\n locale: localeRef,\n showNowIndicator: showNowIndicatorRef,\n slotHeight: slotHeightRef,\n})\n\nfunction navigate(direction: 'prev' | 'next' | 'today') {\n const d = new Date(currentDate.value)\n if (direction === 'today') {\n currentDate.value = new Date()\n } else {\n const delta = direction === 'prev' ? -1 : 1\n if (currentView.value === 'day') d.setDate(d.getDate() + delta)\n else if (currentView.value === 'week') d.setDate(d.getDate() + delta * 7)\n else d.setMonth(d.getMonth() + delta)\n currentDate.value = d\n }\n emit('update:modelValue', currentDate.value)\n emit('navigate', { direction, date: currentDate.value })\n}\n\nfunction setView(view: ScheduleView) {\n currentView.value = view\n emit('update:view', view)\n}\n\nfunction onEventClick(event: ScheduleEvent, e: MouseEvent) {\n e.stopPropagation()\n emit('event-click', event)\n}\n\nfunction onSlotClick(day: Date, slotIndex: number) {\n if (props.readonly || isDragging.value) return\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n const hour = Math.floor(totalMinutes / 60)\n const minute = totalMinutes % 60\n const date = new Date(day)\n date.setHours(hour, minute, 0, 0)\n emit('slot-click', { date, hour, minute })\n}\n\nfunction onSlotPointerDown(day: Date, slotIndex: number, e: PointerEvent) {\n if (props.readonly) return\n const date = new Date(day)\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n date.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0)\n const dayIndex = visibleDays.value.findIndex((d) => isSameDay(d, day))\n startCreate(date, e.clientY, dayIndex)\n}\n\nfunction onEventPointerDown(event: ScheduleEvent, dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startMove(event, e.clientY, dayIndex)\n}\n\nfunction onResizePointerDown(event: ScheduleEvent, edge: 'top' | 'bottom', dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startResize(event, edge, e.clientY, dayIndex)\n}\n\n/**\n * Keyboard alternative to dragging an event: Alt+ArrowUp/Down move it by one\n * slot, Shift+Alt+ArrowUp/Down shorten or lengthen it, Enter/Space open it.\n */\nfunction onEventKeydown(event: ScheduleEvent, e: KeyboardEvent) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n emit('event-click', event)\n return\n }\n if (!e.altKey) return\n const direction = e.key === 'ArrowUp' ? -1 : e.key === 'ArrowDown' ? 1 : 0\n if (direction === 0) return\n\n e.preventDefault()\n const mode = e.shiftKey ? 'resize' : 'move'\n const applied = nudgeEvent(event, direction, mode)\n announce(applied\n ? `${event.title} ${mode === 'move' ? 'moved' : 'resized'} to ${formatEventTime(event)}.`\n : `${event.title} cannot move further.`)\n}\n\n/** Status written next to the time so it never rests on color alone (3c). Confirmed is the default and stays unlabeled. */\nconst STATUS_TEXT: Partial<Record<NonNullable<ScheduleEvent['status']>, string>> = {\n pending: 'Pending',\n 'in-progress': 'In progress',\n cancelled: 'Cancelled',\n}\n\nfunction eventTimeText(event: ScheduleEvent): string {\n const status = event.status && STATUS_TEXT[event.status]\n return status ? `${formatEventTime(event)} · ${status}` : formatEventTime(event)\n}\n\n/** Instructions announced with each event. */\nfunction eventLabel(event: ScheduleEvent): string {\n const base = `${event.title}, ${formatEventTime(event)}`\n if (props.readonly || event.draggable === false) return base\n return `${base}. Press Alt with the up or down arrow keys to reschedule it.`\n}\n\nfunction onMonthDayClick(date: Date) {\n currentDate.value = date\n emit('update:modelValue', date)\n setView('day')\n}\n\nonMounted(() => {\n nowTimer.value = setInterval(() => { now.value = new Date() }, 60000)\n\n // Scroll to current time or 8am\n if (bodyRef.value) {\n const targetHour = now.value.getHours() >= props.dayStartHour && now.value.getHours() <= props.dayEndHour\n ? now.value.getHours()\n : 8\n const offsetMin = (targetHour - props.dayStartHour) * 60\n const pixelsPerMin = SLOT_HEIGHT / props.slotDuration\n bodyRef.value.scrollTop = Math.max(0, offsetMin * pixelsPerMin - 100)\n }\n})\n\nonUnmounted(() => {\n if (nowTimer.value) clearInterval(nowTimer.value)\n})\n</script>\n\n<template>\n <div class=\"mint-schedule\" :style=\"({ '--slot-height': `${SLOT_HEIGHT}px` } as Record<string, string>)\">\n <!-- Header -->\n <div v-if=\"showNavigation || showViewToggle\" class=\"mint-schedule__header\">\n <div v-if=\"showNavigation\" class=\"mint-schedule__nav\">\n <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Previous\"\n @click=\"navigate('prev')\"\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 <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Next\"\n @click=\"navigate('next')\"\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 <button\n type=\"button\"\n class=\"mint-schedule__today-btn\"\n @click=\"navigate('today')\"\n >\n Today\n </button>\n </div>\n\n <span class=\"mint-schedule__title\">{{ headerTitle }}</span>\n\n <div v-if=\"showViewToggle\" class=\"mint-schedule__view-toggle\">\n <button\n v-for=\"v in (['day', 'week', 'month'] as ScheduleView[])\"\n :key=\"v\"\n type=\"button\"\n :class=\"['mint-schedule__view-btn', currentView === v ? 'mint-schedule__view-btn--active' : '']\"\n @click=\"setView(v)\"\n >\n {{ v.charAt(0).toUpperCase() + v.slice(1) }}\n </button>\n </div>\n </div>\n\n <!-- Loading / error / empty placeholders replace the grid -->\n <div v-if=\"error\" class=\"mint-schedule__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" :title=\"error\" />\n </slot>\n </div>\n\n <div v-else-if=\"loading\" class=\"mint-schedule__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" loading-label=\"Loading schedule\" />\n </slot>\n </div>\n\n <div v-else-if=\"showEmptyState\" class=\"mint-schedule__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" :title=\"emptyText\" />\n </slot>\n </div>\n\n <!-- Day / Week View -->\n <template v-else-if=\"currentView === 'day' || currentView === 'week'\">\n <!-- Day headers -->\n <div v-if=\"currentView === 'week'\" class=\"mint-schedule__day-headers\">\n <div class=\"mint-schedule__day-header-gutter\" />\n <div\n v-for=\"(day, i) in visibleDays\"\n :key=\"i\"\n :class=\"['mint-schedule__day-header', isToday(day) ? 'mint-schedule__day-header--today' : '']\"\n >\n <slot name=\"day-header\" :date=\"day\" :isToday=\"isToday(day)\">\n <span class=\"mint-schedule__day-header-name\">\n {{ day.toLocaleDateString(locale, { weekday: 'short' }) }}\n </span>\n <span class=\"mint-schedule__day-header-number\">\n {{ day.getDate() }}\n </span>\n </slot>\n </div>\n </div>\n\n <!-- Time grid body -->\n <div ref=\"bodyRef\" class=\"mint-schedule__body\">\n <!-- Time gutter -->\n <div class=\"mint-schedule__time-gutter\">\n <div\n v-for=\"(label, i) in timeLabels\"\n :key=\"i\"\n class=\"mint-schedule__time-label\"\n >\n <slot name=\"time-label\" :label=\"label\" :index=\"i\">\n {{ i < timeLabels.length - 1 ? label : '' }}\n </slot>\n </div>\n </div>\n\n <!-- Day columns -->\n <div class=\"mint-schedule__day-columns\">\n <div\n v-for=\"(day, dayIdx) in visibleDays\"\n :key=\"dayIdx\"\n :class=\"['mint-schedule__day-column', isToday(day) ? 'mint-schedule__day-column--today' : '']\"\n >\n <!-- Slot grid -->\n <div\n v-for=\"slotIdx in totalSlots\"\n :key=\"slotIdx\"\n :class=\"['mint-schedule__slot', !readonly ? 'mint-schedule__slot--interactive' : '']\"\n @click=\"onSlotClick(day, slotIdx - 1)\"\n @pointerdown=\"onSlotPointerDown(day, slotIdx - 1, $event)\"\n />\n\n <!-- Events -->\n <div\n v-for=\"event in getEventsForDay(day)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__event', getStatusClass(event.status)]\"\n :style=\"getEventStyle(event, day) || undefined\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"eventLabel(event)\"\n @click=\"onEventClick(event, $event)\"\n @keydown=\"onEventKeydown(event, $event)\"\n @pointerdown=\"onEventPointerDown(event, dayIdx, $event)\"\n >\n <slot name=\"event\" :event=\"event\">\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--top\"\n @pointerdown=\"onResizePointerDown(event, 'top', dayIdx, $event)\"\n />\n <div class=\"mint-schedule__event-title\">{{ event.title }}</div>\n <div class=\"mint-schedule__event-time\">{{ eventTimeText(event) }}</div>\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--bottom\"\n @pointerdown=\"onResizePointerDown(event, 'bottom', dayIdx, $event)\"\n />\n </slot>\n </div>\n\n <!-- Blocked slots -->\n <div\n v-for=\"(blocked, bIdx) in getBlockedForDay(day)\"\n :key=\"`b-${bIdx}`\"\n class=\"mint-schedule__blocked\"\n :style=\"getBlockedStyle(blocked)\"\n >\n <span v-if=\"blocked.label\" class=\"mint-schedule__blocked-label\">\n {{ blocked.label }}\n </span>\n </div>\n\n <!-- Now indicator -->\n <div\n v-if=\"showNowIndicator && isToday(day) && getNowIndicatorStyle()\"\n class=\"mint-schedule__now-indicator\"\n :style=\"getNowIndicatorStyle()!\"\n >\n <div class=\"mint-schedule__now-dot\" />\n </div>\n\n <!-- Drag ghost -->\n <div\n v-if=\"isDragging && ghost && ghost.dayIndex === dayIdx\"\n class=\"mint-schedule__ghost\"\n :style=\"ghost.style\"\n />\n </div>\n </div>\n </div>\n </template>\n\n <!-- Month View -->\n <template v-else-if=\"currentView === 'month'\">\n <div class=\"mint-schedule__month-grid\">\n <!-- Weekday headers -->\n <div\n v-for=\"label in monthWeekdayLabels\"\n :key=\"label\"\n class=\"mint-schedule__month-weekday\"\n >\n {{ label }}\n </div>\n\n <!-- Day cells -->\n <div\n v-for=\"(day, i) in monthDays\"\n :key=\"i\"\n :class=\"[\n 'mint-schedule__month-cell',\n isToday(day.date) ? 'mint-schedule__month-cell--today' : '',\n !day.isCurrentMonth ? 'mint-schedule__month-cell--outside' : '',\n ]\"\n @click=\"onMonthDayClick(day.date)\"\n >\n <slot name=\"month-day\" :date=\"day.date\" :isToday=\"isToday(day.date)\" :events=\"getEventsForDate(day.date)\">\n <div class=\"mint-schedule__month-date\">{{ day.date.getDate() }}</div>\n <div\n v-for=\"event in getEventsForDate(day.date).slice(0, 3)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__month-event', getMonthStatusClass(event.status)]\"\n @click.stop=\"onEventClick(event, $event)\"\n >\n {{ event.title }}\n </div>\n <div\n v-if=\"getEventsForDate(day.date).length > 3\"\n class=\"mint-schedule__month-more\"\n >\n +{{ getEventsForDate(day.date).length - 3 }} more\n </div>\n </slot>\n </div>\n </div>\n </template>\n\n <!-- Reschedule announcements for screen readers -->\n <div role=\"status\" aria-live=\"polite\" :style=\"announcerStyle\">{{ announcement }}</div>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/** Drag-and-drop week/day calendar for scheduling lab events in configurable time slots. */\nimport { ref, computed, watch, onMounted, onUnmounted } from 'vue'\nimport type {\n ScheduleView,\n ScheduleEvent,\n ScheduleBlockedSlot,\n} from '../types/components'\nimport { useScheduleDrag } from '../composables/useScheduleDrag'\nimport { useReorderAnnouncer } from '../composables/useListReorder'\nimport {\n SCHEDULE_SLOT_HEIGHT,\n parseScheduleModelDate,\n useScheduleCalendarLayout,\n} from '../composables/useScheduleCalendarLayout'\nimport DataStateInternal from './internal/DataStateInternal.vue'\n\ninterface Props {\n modelValue?: Date | string\n view?: ScheduleView\n events?: ScheduleEvent[]\n dayStartHour?: number\n dayEndHour?: number\n slotDuration?: number\n showNowIndicator?: boolean\n readonly?: boolean\n blockedSlots?: ScheduleBlockedSlot[]\n weekStartsOn?: 0 | 1\n showViewToggle?: boolean\n showNavigation?: boolean\n statusColors?: Record<string, string>\n locale?: string\n /** Replace the calendar grid with a loading placeholder. */\n loading?: boolean\n /**\n * Error message. When set, the calendar grid is replaced by an error state.\n * `null`/`undefined` renders normally.\n */\n error?: string | null\n /**\n * Opt-in empty-state headline. An empty grid is meaningful on a calendar, so\n * the empty state only replaces it when this message is supplied and there\n * are no events.\n */\n emptyText?: string\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n view: 'week',\n events: () => [],\n dayStartHour: 6,\n dayEndHour: 22,\n slotDuration: 30,\n showNowIndicator: true,\n readonly: false,\n blockedSlots: () => [],\n weekStartsOn: 1,\n showViewToggle: true,\n showNavigation: true,\n locale: 'en-US',\n loading: false,\n error: null,\n})\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: Date]\n 'update:view': [value: ScheduleView]\n 'event-click': [event: ScheduleEvent]\n 'event-create': [context: { start: Date; end: Date }]\n 'event-update': [context: { event: ScheduleEvent; newStart: Date; newEnd: Date }]\n 'slot-click': [context: { date: Date; hour: number; minute: number }]\n 'navigate': [context: { direction: string; date: Date }]\n}>()\n\nconst SLOT_HEIGHT = SCHEDULE_SLOT_HEIGHT\nconst currentView = ref<ScheduleView>(props.view)\n\n/** Empty state is opt-in: it needs an `emptyText` and an event-free range. */\nconst showEmptyState = computed(() =>\n Boolean(props.emptyText) && props.events.length === 0,\n)\nconst currentDate = ref<Date>(parseScheduleModelDate(props.modelValue))\nconst nowTimer = ref<ReturnType<typeof setInterval>>()\nconst now = ref(new Date())\nconst bodyRef = ref<HTMLDivElement>()\n\nwatch(() => props.view, (v) => { currentView.value = v })\nwatch(() => props.modelValue, (v) => { if (v) currentDate.value = parseScheduleModelDate(v) })\n\nconst slotHeightRef = computed(() => SLOT_HEIGHT)\nconst slotDurationRef = computed(() => props.slotDuration)\nconst dayStartRef = computed(() => props.dayStartHour)\nconst dayEndRef = computed(() => props.dayEndHour)\nconst readonlyRef = computed(() => props.readonly)\nconst eventsRef = computed(() => props.events)\nconst blockedSlotsRef = computed(() => props.blockedSlots)\nconst weekStartsOnRef = computed(() => props.weekStartsOn)\nconst localeRef = computed(() => props.locale)\nconst showNowIndicatorRef = computed(() => props.showNowIndicator)\n\nconst { announcement, announce, announcerStyle } = useReorderAnnouncer()\n\nconst { isDragging, ghost, startCreate, startMove, startResize, nudgeEvent } = useScheduleDrag({\n slotDuration: slotDurationRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n readonly: readonlyRef,\n slotHeight: slotHeightRef,\n onCreateComplete(start, end) {\n emit('event-create', { start, end })\n },\n onMoveComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n onResizeComplete(event, newStart, newEnd) {\n emit('event-update', { event, newStart, newEnd })\n },\n})\n\nconst {\n totalSlots,\n timeLabels,\n visibleDays,\n headerTitle,\n monthDays,\n monthWeekdayLabels,\n isToday,\n isSameDay,\n getEventStyle,\n getEventsForDay,\n getEventsForDate,\n getBlockedForDay,\n getBlockedStyle,\n getNowIndicatorStyle,\n getStatusClass,\n getMonthStatusClass,\n formatEventTime,\n} = useScheduleCalendarLayout({\n currentView,\n currentDate,\n now,\n events: eventsRef,\n blockedSlots: blockedSlotsRef,\n dayStartHour: dayStartRef,\n dayEndHour: dayEndRef,\n slotDuration: slotDurationRef,\n weekStartsOn: weekStartsOnRef,\n locale: localeRef,\n showNowIndicator: showNowIndicatorRef,\n slotHeight: slotHeightRef,\n})\n\nfunction navigate(direction: 'prev' | 'next' | 'today') {\n const d = new Date(currentDate.value)\n if (direction === 'today') {\n currentDate.value = new Date()\n } else {\n const delta = direction === 'prev' ? -1 : 1\n if (currentView.value === 'day') d.setDate(d.getDate() + delta)\n else if (currentView.value === 'week') d.setDate(d.getDate() + delta * 7)\n else d.setMonth(d.getMonth() + delta)\n currentDate.value = d\n }\n emit('update:modelValue', currentDate.value)\n emit('navigate', { direction, date: currentDate.value })\n}\n\nfunction setView(view: ScheduleView) {\n currentView.value = view\n emit('update:view', view)\n}\n\nfunction onEventClick(event: ScheduleEvent, e: MouseEvent) {\n e.stopPropagation()\n emit('event-click', event)\n}\n\nfunction onSlotClick(day: Date, slotIndex: number) {\n if (props.readonly || isDragging.value) return\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n const hour = Math.floor(totalMinutes / 60)\n const minute = totalMinutes % 60\n const date = new Date(day)\n date.setHours(hour, minute, 0, 0)\n emit('slot-click', { date, hour, minute })\n}\n\nfunction onSlotPointerDown(day: Date, slotIndex: number, e: PointerEvent) {\n if (props.readonly) return\n const date = new Date(day)\n const totalMinutes = props.dayStartHour * 60 + slotIndex * props.slotDuration\n date.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0)\n const dayIndex = visibleDays.value.findIndex((d) => isSameDay(d, day))\n startCreate(date, e.clientY, dayIndex)\n}\n\nfunction onEventPointerDown(event: ScheduleEvent, dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startMove(event, e.clientY, dayIndex)\n}\n\nfunction onResizePointerDown(event: ScheduleEvent, edge: 'top' | 'bottom', dayIndex: number, e: PointerEvent) {\n e.stopPropagation()\n startResize(event, edge, e.clientY, dayIndex)\n}\n\n/**\n * Keyboard alternative to dragging an event: Alt+ArrowUp/Down move it by one\n * slot, Shift+Alt+ArrowUp/Down shorten or lengthen it, Enter/Space open it.\n */\nfunction onEventKeydown(event: ScheduleEvent, e: KeyboardEvent) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n emit('event-click', event)\n return\n }\n if (!e.altKey) return\n const direction = e.key === 'ArrowUp' ? -1 : e.key === 'ArrowDown' ? 1 : 0\n if (direction === 0) return\n\n e.preventDefault()\n const mode = e.shiftKey ? 'resize' : 'move'\n const applied = nudgeEvent(event, direction, mode)\n announce(applied\n ? `${event.title} ${mode === 'move' ? 'moved' : 'resized'} to ${formatEventTime(event)}.`\n : `${event.title} cannot move further.`)\n}\n\n/** Status written next to the time so it never rests on color alone (3c). Confirmed is the default and stays unlabeled. */\nconst STATUS_TEXT: Partial<Record<NonNullable<ScheduleEvent['status']>, string>> = {\n pending: 'Pending',\n 'in-progress': 'In progress',\n cancelled: 'Cancelled',\n}\n\nfunction eventTimeText(event: ScheduleEvent): string {\n const status = event.status && STATUS_TEXT[event.status]\n return status ? `${formatEventTime(event)} · ${status}` : formatEventTime(event)\n}\n\n/** Instructions announced with each event. */\nfunction eventLabel(event: ScheduleEvent): string {\n const base = `${event.title}, ${formatEventTime(event)}`\n if (props.readonly || event.draggable === false) return base\n return `${base}. Press Alt with the up or down arrow keys to reschedule it.`\n}\n\nfunction onMonthDayClick(date: Date) {\n currentDate.value = date\n emit('update:modelValue', date)\n setView('day')\n}\n\nonMounted(() => {\n nowTimer.value = setInterval(() => { now.value = new Date() }, 60000)\n\n // Scroll to current time or 8am\n if (bodyRef.value) {\n const targetHour = now.value.getHours() >= props.dayStartHour && now.value.getHours() <= props.dayEndHour\n ? now.value.getHours()\n : 8\n const offsetMin = (targetHour - props.dayStartHour) * 60\n const pixelsPerMin = SLOT_HEIGHT / props.slotDuration\n bodyRef.value.scrollTop = Math.max(0, offsetMin * pixelsPerMin - 100)\n }\n})\n\nonUnmounted(() => {\n if (nowTimer.value) clearInterval(nowTimer.value)\n})\n</script>\n\n<template>\n <div class=\"mint-schedule\" :style=\"({ '--slot-height': `${SLOT_HEIGHT}px` } as Record<string, string>)\">\n <!-- Header -->\n <div v-if=\"showNavigation || showViewToggle\" class=\"mint-schedule__header\">\n <div v-if=\"showNavigation\" class=\"mint-schedule__nav\">\n <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Previous\"\n @click=\"navigate('prev')\"\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 <button\n type=\"button\"\n class=\"mint-schedule__nav-btn\"\n aria-label=\"Next\"\n @click=\"navigate('next')\"\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 <button\n type=\"button\"\n class=\"mint-schedule__today-btn\"\n @click=\"navigate('today')\"\n >\n Today\n </button>\n </div>\n\n <span class=\"mint-schedule__title\">{{ headerTitle }}</span>\n\n <div v-if=\"showViewToggle\" class=\"mint-schedule__view-toggle\">\n <button\n v-for=\"v in (['day', 'week', 'month'] as ScheduleView[])\"\n :key=\"v\"\n type=\"button\"\n :class=\"['mint-schedule__view-btn', currentView === v ? 'mint-schedule__view-btn--active' : '']\"\n @click=\"setView(v)\"\n >\n {{ v.charAt(0).toUpperCase() + v.slice(1) }}\n </button>\n </div>\n </div>\n\n <!-- Loading / error / empty placeholders replace the grid -->\n <div v-if=\"error\" class=\"mint-schedule__state\">\n <slot name=\"error\" :error=\"error\">\n <DataStateInternal state=\"error\" :title=\"error\" />\n </slot>\n </div>\n\n <div v-else-if=\"loading\" class=\"mint-schedule__state\">\n <slot name=\"loading\">\n <DataStateInternal state=\"loading\" loading-label=\"Loading schedule\" />\n </slot>\n </div>\n\n <div v-else-if=\"showEmptyState\" class=\"mint-schedule__state\">\n <slot name=\"empty\">\n <DataStateInternal state=\"empty\" :title=\"emptyText\" />\n </slot>\n </div>\n\n <!-- Day / Week View -->\n <template v-else-if=\"currentView === 'day' || currentView === 'week'\">\n <!-- Day headers -->\n <div v-if=\"currentView === 'week'\" class=\"mint-schedule__day-headers\">\n <div class=\"mint-schedule__day-header-gutter\" />\n <div\n v-for=\"(day, i) in visibleDays\"\n :key=\"i\"\n :class=\"['mint-schedule__day-header', isToday(day) ? 'mint-schedule__day-header--today' : '']\"\n >\n <slot name=\"day-header\" :date=\"day\" :isToday=\"isToday(day)\">\n <span class=\"mint-schedule__day-header-name\">\n {{ day.toLocaleDateString(locale, { weekday: 'short' }) }}\n </span>\n <span class=\"mint-schedule__day-header-number\">\n {{ day.getDate() }}\n </span>\n </slot>\n </div>\n </div>\n\n <!-- Time grid body -->\n <div ref=\"bodyRef\" class=\"mint-schedule__body\">\n <!-- Time gutter -->\n <div class=\"mint-schedule__time-gutter\">\n <div\n v-for=\"(label, i) in timeLabels\"\n :key=\"i\"\n class=\"mint-schedule__time-label\"\n >\n <slot name=\"time-label\" :label=\"label\" :index=\"i\">\n {{ i < timeLabels.length - 1 ? label : '' }}\n </slot>\n </div>\n </div>\n\n <!-- Day columns -->\n <div class=\"mint-schedule__day-columns\">\n <div\n v-for=\"(day, dayIdx) in visibleDays\"\n :key=\"dayIdx\"\n :class=\"['mint-schedule__day-column', isToday(day) ? 'mint-schedule__day-column--today' : '']\"\n >\n <!-- Slot grid -->\n <div\n v-for=\"slotIdx in totalSlots\"\n :key=\"slotIdx\"\n :class=\"['mint-schedule__slot', !readonly ? 'mint-schedule__slot--interactive' : '']\"\n @click=\"onSlotClick(day, slotIdx - 1)\"\n @pointerdown=\"onSlotPointerDown(day, slotIdx - 1, $event)\"\n />\n\n <!-- Events -->\n <div\n v-for=\"event in getEventsForDay(day)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__event', getStatusClass(event.status)]\"\n :style=\"getEventStyle(event, day) || undefined\"\n role=\"button\"\n tabindex=\"0\"\n :aria-label=\"eventLabel(event)\"\n @click=\"onEventClick(event, $event)\"\n @keydown=\"onEventKeydown(event, $event)\"\n @pointerdown=\"onEventPointerDown(event, dayIdx, $event)\"\n >\n <slot name=\"event\" :event=\"event\">\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--top\"\n @pointerdown=\"onResizePointerDown(event, 'top', dayIdx, $event)\"\n />\n <div class=\"mint-schedule__event-title\">{{ event.title }}</div>\n <div class=\"mint-schedule__event-time\">{{ eventTimeText(event) }}</div>\n <div\n v-if=\"!readonly && event.resizable !== false\"\n class=\"mint-schedule__event-resize-handle mint-schedule__event-resize-handle--bottom\"\n @pointerdown=\"onResizePointerDown(event, 'bottom', dayIdx, $event)\"\n />\n </slot>\n </div>\n\n <!-- Blocked slots -->\n <div\n v-for=\"(blocked, bIdx) in getBlockedForDay(day)\"\n :key=\"`b-${bIdx}`\"\n class=\"mint-schedule__blocked\"\n :style=\"getBlockedStyle(blocked)\"\n >\n <span v-if=\"blocked.label\" class=\"mint-schedule__blocked-label\">\n {{ blocked.label }}\n </span>\n </div>\n\n <!-- Now indicator -->\n <div\n v-if=\"showNowIndicator && isToday(day) && getNowIndicatorStyle()\"\n class=\"mint-schedule__now-indicator\"\n :style=\"getNowIndicatorStyle()!\"\n >\n <div class=\"mint-schedule__now-dot\" />\n </div>\n\n <!-- Drag ghost -->\n <div\n v-if=\"isDragging && ghost && ghost.dayIndex === dayIdx\"\n class=\"mint-schedule__ghost\"\n :style=\"ghost.style\"\n />\n </div>\n </div>\n </div>\n </template>\n\n <!-- Month View -->\n <template v-else-if=\"currentView === 'month'\">\n <div class=\"mint-schedule__month-grid\">\n <!-- Weekday headers -->\n <div\n v-for=\"label in monthWeekdayLabels\"\n :key=\"label\"\n class=\"mint-schedule__month-weekday\"\n >\n {{ label }}\n </div>\n\n <!-- Day cells -->\n <div\n v-for=\"(day, i) in monthDays\"\n :key=\"i\"\n :class=\"[\n 'mint-schedule__month-cell',\n isToday(day.date) ? 'mint-schedule__month-cell--today' : '',\n !day.isCurrentMonth ? 'mint-schedule__month-cell--outside' : '',\n ]\"\n @click=\"onMonthDayClick(day.date)\"\n >\n <slot name=\"month-day\" :date=\"day.date\" :isToday=\"isToday(day.date)\" :events=\"getEventsForDate(day.date)\">\n <div class=\"mint-schedule__month-date\">{{ day.date.getDate() }}</div>\n <div\n v-for=\"event in getEventsForDate(day.date).slice(0, 3)\"\n :key=\"event.id\"\n :class=\"['mint-schedule__month-event', getMonthStatusClass(event.status)]\"\n @click.stop=\"onEventClick(event, $event)\"\n >\n {{ event.title }}\n </div>\n <div\n v-if=\"getEventsForDate(day.date).length > 3\"\n class=\"mint-schedule__month-more\"\n >\n +{{ getEventsForDate(day.date).length - 3 }} more\n </div>\n </slot>\n </div>\n </div>\n </template>\n\n <!-- Reschedule announcements for screen readers -->\n <div role=\"status\" aria-live=\"polite\" :style=\"announcerStyle\">{{ announcement }}</div>\n </div>\n</template>\n","import { ref, computed, watch, type Ref, type ComputedRef } from 'vue'\nimport type { Rack, SlotPosition, WellEditData, WellPlateFormat, WellSampleDropData, Well } from '../types'\nimport { PLATE_CONFIGS, rowIndex, rowLabel as plateRowLabel } from '../components/WellPlate.geometry'\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 /** Swaps two wells (a move when the target is empty). */\n moveWell(rackId: string, sourceWellId: string, targetWellId: string): void\n /** Puts every well in group `groupId` (an empty well becomes a sample); `undefined` empties them. */\n assignWells(rackId: string, wellIds: string[], groupId: string | undefined): void\n\n /**\n * Applies every PlateEditor intent to this state, so the component can hold\n * data it never mutates itself:\n * `<PlateEditor :model-value=\"racks\" :active-rack=\"activeRackId\" v-on=\"plateEditorListeners\" />`\n */\n plateEditorListeners: PlateEditorListeners\n\n /** Steps back through every change to `racks` (at most 50), restoring the plate that was showing. */\n undo(): void\n redo(): void\n canUndo: ComputedRef<boolean>\n canRedo: ComputedRef<boolean>\n /** Forgets every undo and redo step, e.g. after loading unrelated plates. */\n clearHistory(): void\n\n getAllWells(): Array<{ rackId: string; wellId: string; well: Partial<Well> }>\n totalSampleCount: ComputedRef<number>\n reset(): void\n}\n\n/** Handlers for PlateEditor's intent events, keyed by event name (bind with `v-on`). */\nexport interface PlateEditorListeners {\n 'update:activeRack': (rackId: string) => void\n 'rack-add': () => void\n 'rack-remove': (rackId: string) => void\n 'rack-reorder': (rackIds: string[]) => void\n 'update:format': (rackId: string, format: WellPlateFormat) => void\n 'update:slot': (rackId: string, slot: SlotPosition) => void\n 'well-edit': (rackId: string, wellId: string, data: WellEditData) => void\n 'well-clear': (rackId: string, wellId: string) => void\n 'well-move': (rackId: string, sourceWellId: string, targetWellId: string) => void\n 'sample-drop': (rackId: string, wellId: string, data: WellSampleDropData, event: DragEvent) => void\n 'assign': (rackId: string, wellIds: string[], groupId: string | undefined) => void\n 'clear': (rackId: string) => void\n 'fill-series': (rackId: string) => void\n 'undo': () => void\n 'redo': () => void\n}\n\nconst HISTORY_LIMIT = 50\n\nconst SLOT_CYCLE: SlotPosition[] = ['R', 'G', 'B', 'Y']\n\nfunction wellFromEditData(data: WellEditData): Partial<Well> {\n return {\n state: 'filled',\n sampleType: data.sampleType || undefined,\n metadata: {\n label: data.label,\n injectionVolume: data.injectionVolume,\n injectionCount: data.injectionCount,\n customMethod: data.customMethod || null,\n },\n }\n}\n\nfunction wellFromDrop(data: WellSampleDropData, rack: Rack): Partial<Well> | null {\n const label = (data.label ?? data.sampleName ?? data.id ?? '').trim()\n if (!label) return null\n return {\n state: 'filled',\n sampleType: data.sampleType ?? 'sample',\n metadata: {\n label,\n injectionVolume: data.injectionVolume ?? rack.injectionVolume,\n injectionCount: data.injectionCount ?? 1,\n customMethod: data.customMethod ?? null,\n },\n }\n}\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, wells: { ...r.wells } }))\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 wellId = `${plateRowLabel(row)}${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 moveWell(rackId: string, sourceWellId: string, targetWellId: string): void {\n const rack = racks.value.find(r => r.id === rackId)\n if (!rack) return\n const { [sourceWellId]: source, [targetWellId]: target } = rack.wells\n delete rack.wells[sourceWellId]\n delete rack.wells[targetWellId]\n if (target) rack.wells[sourceWellId] = placed(target, sourceWellId)\n if (source) rack.wells[targetWellId] = placed(source, targetWellId)\n }\n\n /** A moved well keeps its position fields in step with its new key. */\n function placed(well: Partial<Well>, wellId: string): Partial<Well> {\n if (well.id === undefined) return well\n const [, row = '', col = ''] = /^([A-Z]+)(\\d+)$/.exec(wellId) ?? []\n return { ...well, id: wellId, row: rowIndex(row), col: Number(col) - 1 }\n }\n\n function assignWells(rackId: string, wellIds: string[], groupId: string | undefined): void {\n const rack = racks.value.find(r => r.id === rackId)\n for (const wellId of wellIds) {\n const sampleType = rack?.wells[wellId]?.sampleType ?? 'sample'\n if (groupId) setWellData(rackId, wellId, { state: 'filled', sampleType, group: groupId })\n else clearWell(rackId, wellId)\n }\n }\n\n // History: the serialized racks before each change, with the plate that was showing.\n // Every change counts, not just the intents: each public mutator call is its own step,\n // and a change made outside them (an import assigning `racks`) becomes one at the next\n // tick, so undo never skips past it.\n interface Snapshot { racks: string; activeRackId: string }\n const undoStack = ref<Snapshot[]>([])\n const redoStack = ref<Snapshot[]>([])\n const canUndo = computed(() => undoStack.value.length > 0)\n const canRedo = computed(() => redoStack.value.length > 0)\n /** The racks as the history last saw them. */\n let known = JSON.stringify(racks.value)\n\n function pushUndo(snapshot: Snapshot): void {\n undoStack.value = [...undoStack.value, snapshot].slice(-HISTORY_LIMIT)\n redoStack.value = []\n }\n\n /** Records a change made since the history last looked, as its own step. */\n function absorb(): void {\n const now = JSON.stringify(racks.value)\n if (now === known) return\n pushUndo({ racks: known, activeRackId: activeRackId.value })\n known = now\n }\n watch(racks, absorb, { deep: true })\n\n function step(from: Ref<Snapshot[]>, to: Ref<Snapshot[]>): void {\n absorb()\n const snapshot = from.value.pop()\n if (snapshot === undefined) return\n to.value.push({ racks: known, activeRackId: activeRackId.value })\n racks.value = JSON.parse(snapshot.racks) as Rack[]\n known = snapshot.racks\n activeRackId.value = racks.value.some(rack => rack.id === snapshot.activeRackId)\n ? snapshot.activeRackId\n : racks.value[0]?.id ?? ''\n }\n\n const undo = (): void => step(undoStack, redoStack)\n const redo = (): void => step(redoStack, undoStack)\n\n function clearHistory(): void {\n undoStack.value = []\n redoStack.value = []\n known = JSON.stringify(racks.value)\n }\n\n /** Runs `apply` and records the prior state when it changed anything. */\n function recorded<Args extends unknown[], Result>(apply: (...args: Args) => Result) {\n return (...args: Args): Result => {\n absorb()\n const before = { racks: known, activeRackId: activeRackId.value }\n const result = apply(...args)\n const after = JSON.stringify(racks.value)\n if (after !== known) {\n pushUndo(before)\n known = after\n }\n return result\n }\n }\n\n const plateEditorListeners: PlateEditorListeners = {\n 'update:activeRack': setActiveRack,\n 'rack-add': recorded(() => { addRack() }),\n 'rack-remove': recorded(removeRack),\n 'rack-reorder': recorded((rackIds: string[]) => {\n racks.value = rackIds.flatMap(id => racks.value.filter(rack => rack.id === id))\n }),\n 'update:format': recorded((rackId: string, format: WellPlateFormat) => updateRack(rackId, { format })),\n 'update:slot': recorded((rackId: string, slot: SlotPosition) => updateRack(rackId, { slot })),\n 'well-edit': recorded((rackId: string, wellId: string, data: WellEditData) => setWellData(rackId, wellId, wellFromEditData(data))),\n 'well-clear': recorded(clearWell),\n 'well-move': recorded(moveWell),\n 'sample-drop': recorded((rackId: string, wellId: string, data: WellSampleDropData) => {\n const rack = racks.value.find(r => r.id === rackId)\n const well = rack && wellFromDrop(data, rack)\n if (well) setWellData(rackId, wellId, well)\n }),\n 'assign': recorded(assignWells),\n 'clear': recorded(clearAllWells),\n 'fill-series': recorded((rackId: string) => fillSeries(rackId)),\n 'undo': undo,\n 'redo': redo,\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 const rack = createDefaultRack('Rack 1', 0)\n racks.value = [rack]\n activeRackId.value = rack.id\n clearHistory()\n }\n\n return {\n racks,\n activeRack,\n activeRackId,\n // Wrapped here, not at the definitions, so assignWells' inner calls stay one step.\n addRack: recorded(addRack),\n removeRack: recorded(removeRack),\n reorderRacks: recorded(reorderRacks),\n updateRack: recorded(updateRack),\n setActiveRack,\n setWellData: recorded(setWellData),\n clearWell: recorded(clearWell),\n clearAllWells: recorded(clearAllWells),\n fillSeries: recorded(fillSeries),\n moveWell: recorded(moveWell),\n assignWells: recorded(assignWells),\n plateEditorListeners,\n undo,\n redo,\n canUndo,\n canRedo,\n clearHistory,\n getAllWells,\n totalSampleCount,\n reset,\n }\n}\n"],"mappings":"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"}