@tetrascience-npm/tetrascience-react-ui 0.5.0-beta.57.1 → 0.5.0-beta.58.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +2 -0
  2. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -0
  3. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +140 -0
  4. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -0
  5. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.cjs +2 -0
  6. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.cjs.map +1 -0
  7. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.js +126 -0
  8. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.js.map +1 -0
  9. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +2 -0
  10. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -0
  11. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +422 -0
  12. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -0
  13. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.cjs +2 -0
  14. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.cjs.map +1 -0
  15. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.js +136 -0
  16. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.js.map +1 -0
  17. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +2 -0
  18. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -0
  19. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +389 -0
  20. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -0
  21. package/dist/components/composed/PlateMapEditor/PlateZoomControl.cjs +2 -0
  22. package/dist/components/composed/PlateMapEditor/PlateZoomControl.cjs.map +1 -0
  23. package/dist/components/composed/PlateMapEditor/PlateZoomControl.js +54 -0
  24. package/dist/components/composed/PlateMapEditor/PlateZoomControl.js.map +1 -0
  25. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.cjs +2 -0
  26. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.cjs.map +1 -0
  27. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.js +96 -0
  28. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.js.map +1 -0
  29. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +2 -0
  30. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -0
  31. package/dist/components/composed/PlateMapEditor/WellLegend.js +58 -0
  32. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -0
  33. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +2 -0
  34. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -0
  35. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +408 -0
  36. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -0
  37. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +2 -0
  38. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -0
  39. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +177 -0
  40. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -0
  41. package/dist/components/composed/PlateMapEditor/autoFill.cjs +2 -0
  42. package/dist/components/composed/PlateMapEditor/autoFill.cjs.map +1 -0
  43. package/dist/components/composed/PlateMapEditor/autoFill.js +41 -0
  44. package/dist/components/composed/PlateMapEditor/autoFill.js.map +1 -0
  45. package/dist/components/composed/PlateMapEditor/csvPlateTriage.cjs +4 -0
  46. package/dist/components/composed/PlateMapEditor/csvPlateTriage.cjs.map +1 -0
  47. package/dist/components/composed/PlateMapEditor/csvPlateTriage.js +103 -0
  48. package/dist/components/composed/PlateMapEditor/csvPlateTriage.js.map +1 -0
  49. package/dist/components/composed/PlateMapEditor/helpers.cjs +2 -0
  50. package/dist/components/composed/PlateMapEditor/helpers.cjs.map +1 -0
  51. package/dist/components/composed/PlateMapEditor/helpers.js +11 -0
  52. package/dist/components/composed/PlateMapEditor/helpers.js.map +1 -0
  53. package/dist/components/composed/PlateMapEditor/wellGrid.cjs +2 -0
  54. package/dist/components/composed/PlateMapEditor/wellGrid.cjs.map +1 -0
  55. package/dist/components/composed/PlateMapEditor/wellGrid.js +56 -0
  56. package/dist/components/composed/PlateMapEditor/wellGrid.js.map +1 -0
  57. package/dist/components/ui/data-table/data-table-filter.cjs +1 -1
  58. package/dist/components/ui/data-table/data-table-filter.cjs.map +1 -1
  59. package/dist/components/ui/data-table/data-table-filter.js +124 -139
  60. package/dist/components/ui/data-table/data-table-filter.js.map +1 -1
  61. package/dist/components/ui/data-table/data-table.cjs +1 -1
  62. package/dist/components/ui/data-table/data-table.cjs.map +1 -1
  63. package/dist/components/ui/data-table/data-table.js +1 -0
  64. package/dist/components/ui/data-table/data-table.js.map +1 -1
  65. package/dist/components/ui/popover.cjs +2 -0
  66. package/dist/components/ui/popover.cjs.map +1 -0
  67. package/dist/components/ui/popover.js +45 -0
  68. package/dist/components/ui/popover.js.map +1 -0
  69. package/dist/index.cjs +1 -1
  70. package/dist/index.css +1 -1
  71. package/dist/index.d.ts +555 -0
  72. package/dist/index.js +637 -595
  73. package/dist/index.js.map +1 -1
  74. package/dist/index.tailwind.css +1 -1
  75. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlatePaintGrid.js","sources":["../../../../src/components/composed/PlateMapEditor/PlatePaintGrid.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { parsePos, pos, rectPositions, resolveDimensions, rowLabel } from \"./wellGrid\";\n\nimport type { PlateDimensions, PlateFormat, WellId, WellRecord } from \"./types\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_CELL = 34;\nconst DEFAULT_MIN_AUTO_CELL = 24;\nconst DEFAULT_MAX_AUTO_CELL = 72;\nconst DEFAULT_MAX_DENSE_AUTO_CELL = 36;\nconst LABEL_PAD = 26;\nconst FRAME_PADDING_PX = 12;\nconst FRAME_BORDER_PX = 1;\nconst LABEL_FONT_SIZE = 16;\nconst LABEL_TEXT_INSET = 9;\nconst LABEL_BASELINE_OFFSET = 5;\nconst WELL_INSET = 1;\nconst STROKE_DEFAULT = 4;\nconst STROKE_SELECTED = 4;\nconst STROKE_HIGHLIGHT = 3;\nconst STROKE_FLASH = 5;\nconst FLASH_DURATION_MS = 650;\nexport const PLATE_MAP_EMPTY_WELL_FILL = \"var(--surface-container)\";\nexport const PLATE_MAP_CELL_BORDER = \"var(--border)\";\n\nexport type WellShape = \"rect\" | \"circle\";\n\nexport interface PlatePaintGridProps<T extends WellRecord = WellRecord> {\n format: PlateFormat;\n rows?: number;\n columns?: number;\n values: Map<WellId, T>;\n selection: Set<WellId>;\n onSelectionChange: (next: Set<WellId>) => void;\n /** Returns the fill color for a given well record (or undefined if empty). */\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n /** Fill color for empty wells. Pass `null` to delegate empty wells to `colorForWell`. */\n emptyWellFillColor?: string | null;\n /** Geometric shape of each well. `\"circle\"` matches scientific plate visuals. Defaults to `\"rect\"`. */\n wellShape?: WellShape;\n /**\n * When true, wraps the grid in a card-like surface (rounded, bordered,\n * padded, soft shadow) so it reads as a physical plate. Pairs well with\n * `wellShape=\"circle\"`.\n */\n framed?: boolean;\n /** Pixel size of each well cell. Defaults to 34 when fixed. */\n cellSize?: number;\n /** Resize wells to fill available width when `cellSize` is not fixed. */\n autoScale?: boolean;\n minCellSize?: number;\n /** Defaults to 72 for 96-well style plates and 36 for denser plates. */\n maxCellSize?: number;\n /** Stroke color for non-selected wells. Defaults to a light border. */\n borderColor?: string;\n /** Stroke color for selected wells. Defaults to the kit primary blue. */\n selectedBorderColor?: string;\n /** Fill color for selected wells. Defaults to the kit primary blue. */\n selectedFillColor?: string;\n /** Selected fill opacity. */\n selectedFillOpacity?: number;\n /** Whether selected wells use the selection fill or keep their assigned well color. */\n selectionFillMode?: \"selection\" | \"well\";\n /** Well id that should briefly flash, usually after a double-click assignment. */\n flashWellId?: WellId;\n /** Changing this value restarts the flash animation for the same well. */\n flashWellKey?: number;\n /**\n * Well ids that should render with a highlight ring. Used for cross-component\n * hover sync (e.g. hovering a legend item to highlight matching wells).\n */\n highlightedWellIds?: ReadonlySet<WellId>;\n /** Stroke color for highlighted wells. Defaults to the kit primary blue. */\n highlightBorderColor?: string;\n onWellHover?: (wellId: WellId | null) => void;\n onWellDoubleClick?: (wellId: WellId) => void;\n /**\n * Optional render-prop invoked once per well. The returned node is placed\n * inside an absolutely-positioned cell on top of the SVG, sized to\n * `cellSize`. The wrapper layer is `pointer-events: none` so the SVG keeps\n * pointer interaction by default; consumers wiring drop targets are expected\n * to set `pointer-events: auto` on their own element while a drag is active.\n */\n wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;\n className?: string;\n}\n\ntype DragMode = \"replace\" | \"add\" | \"remove\";\n\ninterface DragState {\n start: { r: number; c: number };\n cur: { r: number; c: number };\n mode: DragMode;\n}\n\nfunction buildColumnLabels(columns: number, cellSize: number): React.ReactNode[] {\n const labels: React.ReactNode[] = [];\n for (let c = 0; c < columns; c++) {\n labels.push(\n <text\n key={`c${c}`}\n x={LABEL_PAD + c * cellSize + cellSize / 2}\n y={LABEL_PAD / 2}\n textAnchor=\"middle\"\n fontSize={LABEL_FONT_SIZE}\n className=\"fill-muted-foreground\"\n >\n {c + 1}\n </text>,\n );\n }\n return labels;\n}\n\nfunction buildRowLabels(rows: number, cellSize: number): React.ReactNode[] {\n const labels: React.ReactNode[] = [];\n for (let r = 0; r < rows; r++) {\n labels.push(\n <text\n key={`r${r}`}\n x={LABEL_PAD - LABEL_TEXT_INSET}\n y={LABEL_PAD + r * cellSize + cellSize / 2 + LABEL_BASELINE_OFFSET}\n textAnchor=\"end\"\n fontSize={LABEL_FONT_SIZE}\n className=\"fill-muted-foreground\"\n >\n {rowLabel(r)}\n </text>,\n );\n }\n return labels;\n}\n\ninterface BuildWellCellsArgs<T extends WellRecord> {\n dims: PlateDimensions;\n cellSize: number;\n values: Map<WellId, T>;\n selection: Set<WellId>;\n dragPositions: Set<WellId>;\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n emptyWellFillColor: string | null;\n borderColor: string;\n selectedBorderColor: string;\n selectedFillColor: string;\n selectedFillOpacity: number;\n selectionFillMode: \"selection\" | \"well\";\n wellShape: WellShape;\n highlightedWellIds: ReadonlySet<WellId>;\n highlightBorderColor: string;\n flashWellId?: WellId;\n flashWellKey?: number;\n}\n\nfunction buildWellCell<T extends WellRecord>(\n args: BuildWellCellsArgs<T>,\n row: number,\n column: number,\n): React.ReactNode {\n const {\n dims,\n cellSize,\n values,\n selection,\n dragPositions,\n colorForWell,\n emptyWellFillColor,\n selectedFillColor,\n selectedFillOpacity,\n selectionFillMode,\n wellShape,\n } = args;\n const id = pos(row, column, dims.columns);\n const entry = values.get(id);\n const isSelected = selection.has(id) || dragPositions.has(id);\n const wellFill = entry === undefined && emptyWellFillColor !== null ? emptyWellFillColor : colorForWell(entry, id);\n const usesWellFill = isSelected && selectionFillMode === \"well\" && entry !== undefined;\n const usesSelectionFill = isSelected && !usesWellFill;\n const fill = usesSelectionFill ? selectedFillColor : wellFill;\n const fillOpacity = usesSelectionFill ? selectedFillOpacity : undefined;\n\n if (wellShape === \"circle\") {\n const cx = LABEL_PAD + column * cellSize + cellSize / 2;\n const cy = LABEL_PAD + row * cellSize + cellSize / 2;\n const r = cellSize / 2 - WELL_INSET;\n return (\n <circle\n key={id}\n cx={cx}\n cy={cy}\n r={r}\n fill={fill}\n fillOpacity={fillOpacity}\n stroke=\"none\"\n data-well={id}\n data-selected={isSelected ? \"true\" : undefined}\n />\n );\n }\n\n return (\n <rect\n key={id}\n x={LABEL_PAD + column * cellSize}\n y={LABEL_PAD + row * cellSize}\n width={cellSize}\n height={cellSize}\n fill={fill}\n fillOpacity={fillOpacity}\n stroke=\"none\"\n data-well={id}\n data-selected={isSelected ? \"true\" : undefined}\n />\n );\n}\n\nfunction buildWellCells<T extends WellRecord>({ dims, ...args }: BuildWellCellsArgs<T>): React.ReactNode[] {\n const cells: React.ReactNode[] = [];\n for (let r = 0; r < dims.rows; r++) {\n for (let c = 0; c < dims.columns; c++) {\n cells.push(buildWellCell({ dims, ...args }, r, c));\n }\n }\n return cells;\n}\n\nfunction buildGridLines(dims: PlateDimensions, cellSize: number, borderColor: string): React.ReactNode[] {\n const lines: React.ReactNode[] = [];\n const left = LABEL_PAD;\n const top = LABEL_PAD;\n const right = LABEL_PAD + dims.columns * cellSize;\n const bottom = LABEL_PAD + dims.rows * cellSize;\n\n for (let c = 0; c <= dims.columns; c++) {\n const x = LABEL_PAD + c * cellSize;\n lines.push(\n <line\n key={`grid-col-${c}`}\n x1={x}\n y1={top}\n x2={x}\n y2={bottom}\n stroke={borderColor}\n strokeWidth={STROKE_DEFAULT}\n vectorEffect=\"non-scaling-stroke\"\n shapeRendering=\"crispEdges\"\n pointerEvents=\"none\"\n data-plate-grid-line={`column-${c}`}\n />,\n );\n }\n\n for (let r = 0; r <= dims.rows; r++) {\n const y = LABEL_PAD + r * cellSize;\n lines.push(\n <line\n key={`grid-row-${r}`}\n x1={left}\n y1={y}\n x2={right}\n y2={y}\n stroke={borderColor}\n strokeWidth={STROKE_DEFAULT}\n vectorEffect=\"non-scaling-stroke\"\n shapeRendering=\"crispEdges\"\n pointerEvents=\"none\"\n data-plate-grid-line={`row-${r}`}\n />,\n );\n }\n\n return lines;\n}\n\nfunction buildWellOverlay<T extends WellRecord>(\n args: BuildWellCellsArgs<T>,\n row: number,\n column: number,\n): React.ReactNode {\n const {\n dims,\n cellSize,\n selection,\n dragPositions,\n selectedBorderColor,\n flashWellId,\n flashWellKey,\n wellShape,\n highlightedWellIds,\n highlightBorderColor,\n } = args;\n const id = pos(row, column, dims.columns);\n const isSelected = selection.has(id) || dragPositions.has(id);\n const isFlashing = flashWellId === id;\n const isHighlighted = !isSelected && highlightedWellIds.has(id);\n\n if (!isSelected && !isFlashing && !isHighlighted) return null;\n\n const x = LABEL_PAD + column * cellSize + WELL_INSET;\n const y = LABEL_PAD + row * cellSize + WELL_INSET;\n const size = cellSize - WELL_INSET * 2;\n const cx = LABEL_PAD + column * cellSize + cellSize / 2;\n const cy = LABEL_PAD + row * cellSize + cellSize / 2;\n const r = cellSize / 2 - WELL_INSET;\n const isCircle = wellShape === \"circle\";\n\n type WellShapeProps = Record<string, string | number | undefined>;\n const shapeProps: WellShapeProps = isCircle\n ? { cx, cy, r }\n : { x, y, width: size, height: size };\n\n const renderShape = (\n extraProps: Record<string, string | number | undefined>,\n children?: React.ReactNode,\n elementKey?: string | number,\n ): React.ReactNode => {\n const props = { ...shapeProps, ...extraProps, pointerEvents: \"none\" as const };\n return isCircle ? (\n <circle key={elementKey} {...props}>\n {children}\n </circle>\n ) : (\n <rect key={elementKey} {...props}>\n {children}\n </rect>\n );\n };\n\n const flashAnimations = (\n <>\n <animate attributeName=\"fill-opacity\" values=\"0.24;0.1;0\" dur={`${FLASH_DURATION_MS}ms`} fill=\"freeze\" />\n <animate attributeName=\"stroke-opacity\" values=\"0.92;0.42;0\" dur={`${FLASH_DURATION_MS}ms`} fill=\"freeze\" />\n <animate\n attributeName=\"stroke-width\"\n values={`${STROKE_FLASH};${STROKE_SELECTED}`}\n dur={`${FLASH_DURATION_MS}ms`}\n fill=\"freeze\"\n />\n </>\n );\n\n return (\n <g key={`overlay-${id}`}>\n {isSelected\n ? renderShape({\n fill: \"none\",\n stroke: selectedBorderColor,\n strokeWidth: STROKE_SELECTED,\n \"data-well-selection\": id,\n })\n : null}\n {isHighlighted\n ? renderShape({\n fill: \"none\",\n stroke: highlightBorderColor,\n strokeWidth: STROKE_HIGHLIGHT,\n \"data-well-highlight\": id,\n })\n : null}\n {isFlashing\n ? renderShape(\n {\n fill: selectedBorderColor,\n fillOpacity: 0.24,\n stroke: selectedBorderColor,\n strokeOpacity: 0.92,\n strokeWidth: STROKE_FLASH,\n \"data-well-flash\": id,\n },\n flashAnimations,\n `${id}-${flashWellKey}`,\n )\n : null}\n </g>\n );\n}\n\nfunction buildWellOverlays<T extends WellRecord>({ dims, ...args }: BuildWellCellsArgs<T>): React.ReactNode[] {\n const overlays: React.ReactNode[] = [];\n for (let r = 0; r < dims.rows; r++) {\n for (let c = 0; c < dims.columns; c++) {\n const overlay = buildWellOverlay({ dims, ...args }, r, c);\n if (overlay) overlays.push(overlay);\n }\n }\n return overlays;\n}\n\n/**\n * Interactive plate grid with drag-rectangle selection.\n * - Click & drag: replace selection\n * - Shift + drag: add to selection\n * - Alt + drag: remove from selection\n */\nexport function PlatePaintGrid<T extends WellRecord = WellRecord>({\n format,\n rows,\n columns,\n values,\n selection,\n onSelectionChange,\n colorForWell,\n emptyWellFillColor = PLATE_MAP_EMPTY_WELL_FILL,\n wellShape = \"rect\",\n framed = false,\n cellSize,\n autoScale = true,\n minCellSize = DEFAULT_MIN_AUTO_CELL,\n maxCellSize,\n borderColor = PLATE_MAP_CELL_BORDER,\n selectedBorderColor = \"var(--color-primary)\",\n selectedFillColor = \"var(--color-primary)\",\n selectedFillOpacity = 0.18,\n selectionFillMode = \"selection\",\n flashWellId,\n flashWellKey,\n highlightedWellIds,\n highlightBorderColor = \"var(--color-primary)\",\n onWellHover,\n onWellDoubleClick,\n wrapWell,\n className,\n}: PlatePaintGridProps<T>) {\n const dims = resolveDimensions(format, rows, columns);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const svgRef = React.useRef<SVGSVGElement>(null);\n const [drag, setDrag] = React.useState<DragState | null>(null);\n const [containerWidth, setContainerWidth] = React.useState<number>();\n\n React.useLayoutEffect(() => {\n const node = containerRef.current;\n if (!node || !autoScale || cellSize !== undefined) return;\n\n const update = () => setContainerWidth(node.clientWidth);\n update();\n\n if (typeof ResizeObserver === \"undefined\") return;\n\n const observer = new ResizeObserver(update);\n observer.observe(node);\n return () => observer.disconnect();\n }, [autoScale, cellSize]);\n\n const resolvedCellSize = React.useMemo(() => {\n if (!autoScale || cellSize !== undefined || !containerWidth) {\n return cellSize ?? DEFAULT_CELL;\n }\n const autoMaxCellSize = maxCellSize ?? (dims.columns > 12 ? DEFAULT_MAX_DENSE_AUTO_CELL : DEFAULT_MAX_AUTO_CELL);\n const frameAdjust = framed ? (FRAME_PADDING_PX + FRAME_BORDER_PX) * 2 : 0;\n const fitSize = Math.floor((containerWidth - LABEL_PAD - frameAdjust) / dims.columns);\n return Math.max(minCellSize, Math.min(autoMaxCellSize, fitSize));\n }, [autoScale, cellSize, containerWidth, dims.columns, framed, maxCellSize, minCellSize]);\n\n const cellAt = React.useCallback(\n (evt: React.MouseEvent): { r: number; c: number } | null => {\n const svg = svgRef.current;\n if (!svg) return null;\n const rect = svg.getBoundingClientRect();\n const x = evt.clientX - rect.left - LABEL_PAD;\n const y = evt.clientY - rect.top - LABEL_PAD;\n const c = Math.floor(x / resolvedCellSize);\n const r = Math.floor(y / resolvedCellSize);\n if (r < 0 || r >= dims.rows || c < 0 || c >= dims.columns) return null;\n return { r, c };\n },\n [resolvedCellSize, dims.rows, dims.columns],\n );\n\n const handleDown = (e: React.MouseEvent) => {\n const cell = cellAt(e);\n if (!cell) return;\n const mode: DragMode = e.shiftKey ? \"add\" : e.altKey ? \"remove\" : \"replace\";\n setDrag({ start: cell, cur: cell, mode });\n };\n\n const handleMove = (e: React.MouseEvent) => {\n const cell = cellAt(e);\n if (cell && onWellHover) {\n onWellHover(pos(cell.r, cell.c, dims.columns));\n }\n if (!drag || !cell) return;\n setDrag({ ...drag, cur: cell });\n };\n\n const commitDrag = React.useCallback(() => {\n if (!drag) return;\n const positions = rectPositions(drag.start.r, drag.start.c, drag.cur.r, drag.cur.c, dims.columns);\n let next: Set<WellId>;\n if (drag.mode === \"replace\") {\n next = new Set(positions);\n } else if (drag.mode === \"add\") {\n next = new Set(selection);\n positions.forEach((p) => next.add(p));\n } else {\n next = new Set(selection);\n positions.forEach((p) => next.delete(p));\n }\n onSelectionChange(next);\n setDrag(null);\n }, [drag, dims.columns, onSelectionChange, selection]);\n\n const handleUp = () => commitDrag();\n const handleLeave = () => {\n commitDrag();\n onWellHover?.(null);\n };\n const handleDoubleClick = (e: React.MouseEvent) => {\n const cell = cellAt(e);\n if (!cell) return;\n onWellDoubleClick?.(pos(cell.r, cell.c, dims.columns));\n };\n\n const dragPositions = React.useMemo(() => {\n if (!drag) return new Set<WellId>();\n return new Set(rectPositions(drag.start.r, drag.start.c, drag.cur.r, drag.cur.c, dims.columns));\n }, [drag, dims.columns]);\n\n // Right and bottom edge strokes sit on the plate boundary; give the SVG room so they are not clipped.\n const edgeStrokePadding = STROKE_DEFAULT;\n const width = dims.columns * resolvedCellSize + LABEL_PAD + edgeStrokePadding;\n const height = dims.rows * resolvedCellSize + LABEL_PAD + edgeStrokePadding;\n\n const colLabels = buildColumnLabels(dims.columns, resolvedCellSize);\n const rowLabels = buildRowLabels(dims.rows, resolvedCellSize);\n const resolvedHighlightedWellIds: ReadonlySet<WellId> = highlightedWellIds ?? new Set();\n const wellRenderArgs = {\n dims,\n cellSize: resolvedCellSize,\n values,\n selection,\n dragPositions,\n colorForWell,\n emptyWellFillColor,\n borderColor,\n selectedBorderColor,\n selectedFillColor,\n selectedFillOpacity,\n selectionFillMode,\n wellShape,\n highlightedWellIds: resolvedHighlightedWellIds,\n highlightBorderColor,\n flashWellId,\n flashWellKey,\n };\n const wellCells = buildWellCells(wellRenderArgs);\n const gridLines = wellShape === \"circle\" ? [] : buildGridLines(dims, resolvedCellSize, borderColor);\n const wellOverlays = buildWellOverlays(wellRenderArgs);\n\n const overlayCells = React.useMemo<React.ReactNode[]>(() => {\n if (!wrapWell) return [];\n const cells: React.ReactNode[] = [];\n for (let r = 0; r < dims.rows; r++) {\n for (let c = 0; c < dims.columns; c++) {\n const id = pos(r, c, dims.columns);\n cells.push(\n <div\n key={id}\n style={{\n position: \"absolute\",\n left: LABEL_PAD + c * resolvedCellSize,\n top: LABEL_PAD + r * resolvedCellSize,\n width: resolvedCellSize,\n height: resolvedCellSize,\n }}\n data-well-id={id}\n >\n {wrapWell(id, resolvedCellSize)}\n </div>,\n );\n }\n }\n return cells;\n }, [wrapWell, dims.rows, dims.columns, resolvedCellSize]);\n\n return (\n <div\n ref={containerRef}\n className={cn(\"relative w-full select-none\", className)}\n data-slot=\"plate-paint-grid\"\n >\n <div\n className={cn(\n \"relative inline-block\",\n framed && \"rounded-xl border bg-card p-3 shadow-sm\",\n )}\n data-slot=\"plate-paint-grid-frame\"\n >\n <svg\n ref={svgRef}\n width={width}\n height={height}\n className=\"block cursor-crosshair\"\n onMouseDown={handleDown}\n onMouseMove={handleMove}\n onMouseUp={handleUp}\n onMouseLeave={handleLeave}\n onDoubleClick={handleDoubleClick}\n role=\"group\"\n aria-label={`${dims.rows} row by ${dims.columns} column plate map. Drag to select wells.`}\n >\n {colLabels}\n {rowLabels}\n {wellCells}\n {gridLines}\n {wellOverlays}\n </svg>\n {wrapWell ? (\n <div\n className=\"pointer-events-none absolute\"\n style={{\n top: framed ? FRAME_PADDING_PX : 0,\n left: framed ? FRAME_PADDING_PX : 0,\n width,\n height,\n }}\n aria-hidden\n data-slot=\"plate-well-overlay\"\n >\n {overlayCells}\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport { parsePos };\n"],"names":["DEFAULT_CELL","DEFAULT_MIN_AUTO_CELL","DEFAULT_MAX_AUTO_CELL","DEFAULT_MAX_DENSE_AUTO_CELL","LABEL_PAD","FRAME_PADDING_PX","FRAME_BORDER_PX","LABEL_FONT_SIZE","LABEL_TEXT_INSET","LABEL_BASELINE_OFFSET","WELL_INSET","STROKE_DEFAULT","STROKE_SELECTED","STROKE_HIGHLIGHT","STROKE_FLASH","FLASH_DURATION_MS","PLATE_MAP_EMPTY_WELL_FILL","PLATE_MAP_CELL_BORDER","buildColumnLabels","columns","cellSize","labels","c","jsx","buildRowLabels","rows","r","buildWellCell","args","row","column","dims","values","selection","dragPositions","colorForWell","emptyWellFillColor","selectedFillColor","selectedFillOpacity","selectionFillMode","wellShape","id","pos","entry","isSelected","wellFill","usesSelectionFill","fill","fillOpacity","cx","cy","buildWellCells","cells","buildGridLines","borderColor","lines","left","top","right","bottom","x","y","buildWellOverlay","selectedBorderColor","flashWellId","flashWellKey","highlightedWellIds","highlightBorderColor","isFlashing","isHighlighted","size","isCircle","shapeProps","renderShape","extraProps","children","elementKey","props","flashAnimations","jsxs","Fragment","buildWellOverlays","overlays","overlay","PlatePaintGrid","format","onSelectionChange","framed","autoScale","minCellSize","maxCellSize","onWellHover","onWellDoubleClick","wrapWell","className","resolveDimensions","containerRef","React","svgRef","drag","setDrag","containerWidth","setContainerWidth","node","update","observer","resolvedCellSize","autoMaxCellSize","frameAdjust","fitSize","cellAt","evt","svg","rect","handleDown","e","cell","mode","handleMove","commitDrag","positions","rectPositions","next","p","handleUp","handleLeave","handleDoubleClick","edgeStrokePadding","width","height","colLabels","rowLabels","wellRenderArgs","wellCells","gridLines","wellOverlays","overlayCells","cn"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),a=require("lucide-react"),s=require("../../ui/button.cjs"),l=require("../../ui/button-group.cjs"),b=require("../../../lib/utils.cjs"),x=2;function h(t,o,n){return Math.min(n,Math.max(o,t))}function j({zoom:t,onZoomChange:o,step:n=.1,min:r=.5,max:i=2,showReadout:c=!0,className:d}){const u=m=>o(h(Number(m.toFixed(x)),r,i));return e.jsxs(l.ButtonGroup,{className:b.cn(d),"data-slot":"plate-zoom-control",children:[e.jsx(s.Button,{type:"button",variant:"outline",size:"icon-sm","aria-label":"Zoom out",disabled:t<=r,onClick:()=>u(t-n),children:e.jsx(a.ZoomOut,{"aria-hidden":!0})}),c?e.jsxs(l.ButtonGroupText,{className:"min-w-[3.25rem] justify-center text-xs tabular-nums",children:[Math.round(t*100),"%"]}):null,e.jsx(s.Button,{type:"button",variant:"outline",size:"icon-sm","aria-label":"Zoom in",disabled:t>=i,onClick:()=>u(t+n),children:e.jsx(a.ZoomIn,{"aria-hidden":!0})})]})}exports.PlateZoomControl=j;
2
+ //# sourceMappingURL=PlateZoomControl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlateZoomControl.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateZoomControl.tsx"],"sourcesContent":["import { ZoomIn, ZoomOut } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ButtonGroup, ButtonGroupText } from \"@/components/ui/button-group\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateZoomControlProps {\n zoom: number;\n onZoomChange: (next: number) => void;\n /** Increment per click. Defaults to 0.1. */\n step?: number;\n min?: number;\n max?: number;\n /** Render a percentage readout between the buttons. Defaults to true. */\n showReadout?: boolean;\n className?: string;\n}\n\nconst ZOOM_DECIMALS = 2;\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nexport function PlateZoomControl({\n zoom,\n onZoomChange,\n step = 0.1,\n min = 0.5,\n max = 2,\n showReadout = true,\n className,\n}: PlateZoomControlProps) {\n const setZoom = (next: number) => onZoomChange(clamp(Number(next.toFixed(ZOOM_DECIMALS)), min, max));\n\n return (\n <ButtonGroup className={cn(className)} data-slot=\"plate-zoom-control\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom out\"\n disabled={zoom <= min}\n onClick={() => setZoom(zoom - step)}\n >\n <ZoomOut aria-hidden />\n </Button>\n {showReadout ? (\n <ButtonGroupText className=\"min-w-[3.25rem] justify-center text-xs tabular-nums\">\n {Math.round(zoom * 100)}%\n </ButtonGroupText>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom in\"\n disabled={zoom >= max}\n onClick={() => setZoom(zoom + step)}\n >\n <ZoomIn aria-hidden />\n </Button>\n </ButtonGroup>\n );\n}\n"],"names":["ZOOM_DECIMALS","clamp","value","min","max","PlateZoomControl","zoom","onZoomChange","step","showReadout","className","setZoom","next","ButtonGroup","cn","jsx","Button","ZoomOut","jsxs","ButtonGroupText","ZoomIn"],"mappings":"2PAkBMA,EAAgB,EAEtB,SAASC,EAAMC,EAAeC,EAAaC,EAAqB,CAC9D,OAAO,KAAK,IAAIA,EAAK,KAAK,IAAID,EAAKD,CAAK,CAAC,CAC3C,CAEO,SAASG,EAAiB,CAC/B,KAAAC,EACA,aAAAC,EACA,KAAAC,EAAO,GACP,IAAAL,EAAM,GACN,IAAAC,EAAM,EACN,YAAAK,EAAc,GACd,UAAAC,CACF,EAA0B,CACxB,MAAMC,EAAWC,GAAiBL,EAAaN,EAAM,OAAOW,EAAK,QAAQZ,CAAa,CAAC,EAAGG,EAAKC,CAAG,CAAC,EAEnG,cACGS,cAAA,CAAY,UAAWC,EAAAA,GAAGJ,CAAS,EAAG,YAAU,qBAC/C,SAAA,CAAAK,EAAAA,IAACC,EAAAA,OAAA,CACC,KAAK,SACL,QAAQ,UACR,KAAK,UACL,aAAW,WACX,SAAUV,GAAQH,EAClB,QAAS,IAAMQ,EAAQL,EAAOE,CAAI,EAElC,SAAAO,EAAAA,IAACE,EAAAA,QAAA,CAAQ,cAAW,EAAA,CAAC,CAAA,CAAA,EAEtBR,EACCS,EAAAA,KAACC,EAAAA,gBAAA,CAAgB,UAAU,sDACxB,SAAA,CAAA,KAAK,MAAMb,EAAO,GAAG,EAAE,GAAA,CAAA,CAC1B,EACE,KACJS,EAAAA,IAACC,EAAAA,OAAA,CACC,KAAK,SACL,QAAQ,UACR,KAAK,UACL,aAAW,UACX,SAAUV,GAAQF,EAClB,QAAS,IAAMO,EAAQL,EAAOE,CAAI,EAElC,SAAAO,EAAAA,IAACK,EAAAA,OAAA,CAAO,cAAW,EAAA,CAAC,CAAA,CAAA,CACtB,EACF,CAEJ"}
@@ -0,0 +1,54 @@
1
+ import { jsxs as l, jsx as n } from "react/jsx-runtime";
2
+ import { ZoomOut as d, ZoomIn as p } from "lucide-react";
3
+ import { Button as u } from "../../ui/button.js";
4
+ import { ButtonGroup as h, ButtonGroupText as b } from "../../ui/button-group.js";
5
+ import { cn as f } from "../../../lib/utils.js";
6
+ const x = 2;
7
+ function Z(t, r, o) {
8
+ return Math.min(o, Math.max(r, t));
9
+ }
10
+ function N({
11
+ zoom: t,
12
+ onZoomChange: r,
13
+ step: o = 0.1,
14
+ min: e = 0.5,
15
+ max: i = 2,
16
+ showReadout: m = !0,
17
+ className: s
18
+ }) {
19
+ const a = (c) => r(Z(Number(c.toFixed(x)), e, i));
20
+ return /* @__PURE__ */ l(h, { className: f(s), "data-slot": "plate-zoom-control", children: [
21
+ /* @__PURE__ */ n(
22
+ u,
23
+ {
24
+ type: "button",
25
+ variant: "outline",
26
+ size: "icon-sm",
27
+ "aria-label": "Zoom out",
28
+ disabled: t <= e,
29
+ onClick: () => a(t - o),
30
+ children: /* @__PURE__ */ n(d, { "aria-hidden": !0 })
31
+ }
32
+ ),
33
+ m ? /* @__PURE__ */ l(b, { className: "min-w-[3.25rem] justify-center text-xs tabular-nums", children: [
34
+ Math.round(t * 100),
35
+ "%"
36
+ ] }) : null,
37
+ /* @__PURE__ */ n(
38
+ u,
39
+ {
40
+ type: "button",
41
+ variant: "outline",
42
+ size: "icon-sm",
43
+ "aria-label": "Zoom in",
44
+ disabled: t >= i,
45
+ onClick: () => a(t + o),
46
+ children: /* @__PURE__ */ n(p, { "aria-hidden": !0 })
47
+ }
48
+ )
49
+ ] });
50
+ }
51
+ export {
52
+ N as PlateZoomControl
53
+ };
54
+ //# sourceMappingURL=PlateZoomControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlateZoomControl.js","sources":["../../../../src/components/composed/PlateMapEditor/PlateZoomControl.tsx"],"sourcesContent":["import { ZoomIn, ZoomOut } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ButtonGroup, ButtonGroupText } from \"@/components/ui/button-group\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateZoomControlProps {\n zoom: number;\n onZoomChange: (next: number) => void;\n /** Increment per click. Defaults to 0.1. */\n step?: number;\n min?: number;\n max?: number;\n /** Render a percentage readout between the buttons. Defaults to true. */\n showReadout?: boolean;\n className?: string;\n}\n\nconst ZOOM_DECIMALS = 2;\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nexport function PlateZoomControl({\n zoom,\n onZoomChange,\n step = 0.1,\n min = 0.5,\n max = 2,\n showReadout = true,\n className,\n}: PlateZoomControlProps) {\n const setZoom = (next: number) => onZoomChange(clamp(Number(next.toFixed(ZOOM_DECIMALS)), min, max));\n\n return (\n <ButtonGroup className={cn(className)} data-slot=\"plate-zoom-control\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom out\"\n disabled={zoom <= min}\n onClick={() => setZoom(zoom - step)}\n >\n <ZoomOut aria-hidden />\n </Button>\n {showReadout ? (\n <ButtonGroupText className=\"min-w-[3.25rem] justify-center text-xs tabular-nums\">\n {Math.round(zoom * 100)}%\n </ButtonGroupText>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom in\"\n disabled={zoom >= max}\n onClick={() => setZoom(zoom + step)}\n >\n <ZoomIn aria-hidden />\n </Button>\n </ButtonGroup>\n );\n}\n"],"names":["ZOOM_DECIMALS","clamp","value","min","max","PlateZoomControl","zoom","onZoomChange","step","showReadout","className","setZoom","next","ButtonGroup","cn","jsx","Button","ZoomOut","jsxs","ButtonGroupText","ZoomIn"],"mappings":";;;;;AAkBA,MAAMA,IAAgB;AAEtB,SAASC,EAAMC,GAAeC,GAAaC,GAAqB;AAC9D,SAAO,KAAK,IAAIA,GAAK,KAAK,IAAID,GAAKD,CAAK,CAAC;AAC3C;AAEO,SAASG,EAAiB;AAAA,EAC/B,MAAAC;AAAA,EACA,cAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,KAAAL,IAAM;AAAA,EACN,KAAAC,IAAM;AAAA,EACN,aAAAK,IAAc;AAAA,EACd,WAAAC;AACF,GAA0B;AACxB,QAAMC,IAAU,CAACC,MAAiBL,EAAaN,EAAM,OAAOW,EAAK,QAAQZ,CAAa,CAAC,GAAGG,GAAKC,CAAG,CAAC;AAEnG,2BACGS,GAAA,EAAY,WAAWC,EAAGJ,CAAS,GAAG,aAAU,sBAC/C,UAAA;AAAA,IAAA,gBAAAK;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,cAAW;AAAA,QACX,UAAUV,KAAQH;AAAA,QAClB,SAAS,MAAMQ,EAAQL,IAAOE,CAAI;AAAA,QAElC,UAAA,gBAAAO,EAACE,GAAA,EAAQ,eAAW,GAAA,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAEtBR,IACC,gBAAAS,EAACC,GAAA,EAAgB,WAAU,uDACxB,UAAA;AAAA,MAAA,KAAK,MAAMb,IAAO,GAAG;AAAA,MAAE;AAAA,IAAA,EAAA,CAC1B,IACE;AAAA,IACJ,gBAAAS;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,cAAW;AAAA,QACX,UAAUV,KAAQF;AAAA,QAClB,SAAS,MAAMO,EAAQL,IAAOE,CAAI;AAAA,QAElC,UAAA,gBAAAO,EAACK,GAAA,EAAO,eAAW,GAAA,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EACtB,GACF;AAEJ;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),j=require("lucide-react"),O=require("react"),q=require("./csvPlateTriage.cjs"),P=require("./helpers.cjs"),m=require("../../ui/button.cjs"),k=require("../../ui/label.cjs"),s=require("../../ui/select.cjs"),f=require("../../ui/separator.cjs"),y=require("../../../lib/utils.cjs");function w(t){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const l in t)if(l!=="default"){const i=Object.getOwnPropertyDescriptor(t,l);Object.defineProperty(a,l,i.get?i:{enumerable:!0,get:()=>t[l]})}}return a.default=t,Object.freeze(a)}const g=w(O);function h({label:t,accept:a,onPick:l,disabled:i,triageCsv:o}){const r=g.useRef(null);return e.jsxs(e.Fragment,{children:[e.jsxs(m.Button,{type:"button",variant:"outline",size:"sm",disabled:i,onClick:()=>r.current?.click(),children:[e.jsx(j.Upload,{"aria-hidden":!0})," ",t]}),e.jsx("input",{ref:r,type:"file",accept:a,hidden:!0,onChange:c=>{const u=c.currentTarget,n=c.target.files?.[0];if(!n){u.value="";return}(async()=>{const x=o?await q.triagePlateMapCsvFile(n):void 0;await(x?l(n,x):l(n)),u.value=""})()}})]})}function z({templates:t,templateId:a,onTemplateChange:l,onClearTemplate:i,hasEntries:o,onImportCsv:r,onExportCsv:c,onImportTemplate:u,onExportTemplate:n,className:x,csvAccept:v=".csv,text/csv",templateAccept:b="application/json"}){const S=g.useMemo(()=>P.groupTemplateOptions(t),[t]);return e.jsxs("div",{"data-slot":"template-io-panel",className:y.cn("flex flex-col gap-3",x),children:[e.jsx("div",{className:"text-sm font-medium",children:"Template & import / export"}),t&&l?e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(k.Label,{htmlFor:"plate-template",children:"Template"}),e.jsxs(s.Select,{value:a??"",onValueChange:d=>l(d),children:[e.jsx(s.SelectTrigger,{id:"plate-template",size:"sm",className:"w-full",children:e.jsx(s.SelectValue,{placeholder:"Select template…"})}),e.jsx(s.SelectContent,{children:S.map(([d,N])=>e.jsxs(s.SelectGroup,{children:[d?e.jsx(s.SelectLabel,{children:d}):null,N.map(p=>e.jsx(s.SelectItem,{value:p.id,children:p.label},p.id))]},d||"_"))})]}),i?e.jsx(m.Button,{variant:"outline",size:"sm",onClick:i,disabled:!a&&!o,children:"Clear template"}):null]}):null,u||n?e.jsxs(e.Fragment,{children:[e.jsx(f.Separator,{}),e.jsx("div",{className:"text-xs font-medium text-muted-foreground",children:"Template"}),e.jsxs("div",{className:"flex flex-col gap-2",children:[u?e.jsx(h,{label:"Import template (JSON)",accept:b,onPick:u}):null,n?e.jsxs(m.Button,{variant:"outline",size:"sm",disabled:!o,onClick:n,children:[e.jsx(j.Download,{"aria-hidden":!0})," Export template (JSON)"]}):null]})]}):null,r||c?e.jsxs(e.Fragment,{children:[e.jsx(f.Separator,{}),e.jsx("div",{className:"text-xs font-medium text-muted-foreground",children:"Plate map"}),e.jsxs("div",{className:"flex flex-col gap-2",children:[r?e.jsx(h,{label:"Import plate map (CSV)",accept:v,onPick:r,triageCsv:!0}):null,c?e.jsxs(m.Button,{variant:"outline",size:"sm",disabled:!o,onClick:c,children:[e.jsx(j.Download,{"aria-hidden":!0})," Export plate map (CSV)"]}):null]})]}):null]})}exports.TemplateIOPanel=z;
2
+ //# sourceMappingURL=TemplateIOPanel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateIOPanel.cjs","sources":["../../../../src/components/composed/PlateMapEditor/TemplateIOPanel.tsx"],"sourcesContent":["import { Download, Upload } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { triagePlateMapCsvFile } from \"./csvPlateTriage\";\nimport { groupTemplateOptions } from \"./helpers\";\n\nimport type { ImportExportHandlers, PlateMapCsvTriage, TemplateOption } from \"./types\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface TemplateIOPanelProps extends ImportExportHandlers {\n templates?: TemplateOption[];\n templateId?: string;\n onTemplateChange?: (id: string) => void;\n onClearTemplate?: () => void;\n /** Disable export buttons when there's nothing to export. */\n hasEntries?: boolean;\n className?: string;\n csvAccept?: string;\n templateAccept?: string;\n}\n\ninterface FilePickerButtonProps {\n label: string;\n accept: string;\n onPick: (file: File, triage?: PlateMapCsvTriage) => void | Promise<void>;\n disabled?: boolean;\n triageCsv?: boolean;\n}\n\nfunction FilePickerButton({ label, accept, onPick, disabled, triageCsv }: FilePickerButtonProps) {\n const inputRef = React.useRef<HTMLInputElement>(null);\n return (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={disabled} onClick={() => inputRef.current?.click()}>\n <Upload aria-hidden /> {label}\n </Button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n hidden\n onChange={(e) => {\n const input = e.currentTarget;\n const f = e.target.files?.[0];\n if (!f) {\n input.value = \"\";\n return;\n }\n\n void (async () => {\n const triage = triageCsv ? await triagePlateMapCsvFile(f) : undefined;\n await (triage ? onPick(f, triage) : onPick(f));\n input.value = \"\";\n })();\n }}\n />\n </>\n );\n}\n\nexport function TemplateIOPanel({\n templates,\n templateId,\n onTemplateChange,\n onClearTemplate,\n hasEntries,\n onImportCsv,\n onExportCsv,\n onImportTemplate,\n onExportTemplate,\n className,\n csvAccept = \".csv,text/csv\",\n templateAccept = \"application/json\",\n}: TemplateIOPanelProps) {\n const groups = React.useMemo(() => groupTemplateOptions(templates), [templates]);\n\n return (\n <div data-slot=\"template-io-panel\" className={cn(\"flex flex-col gap-3\", className)}>\n <div className=\"text-sm font-medium\">Template &amp; import / export</div>\n\n {templates && onTemplateChange ? (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"plate-template\">Template</Label>\n <Select value={templateId ?? \"\"} onValueChange={(v) => onTemplateChange(v)}>\n <SelectTrigger id=\"plate-template\" size=\"sm\" className=\"w-full\">\n <SelectValue placeholder=\"Select template…\" />\n </SelectTrigger>\n <SelectContent>\n {groups.map(([group, opts]) => (\n <SelectGroup key={group || \"_\"}>\n {group ? <SelectLabel>{group}</SelectLabel> : null}\n {opts.map((t) => (\n <SelectItem key={t.id} value={t.id}>\n {t.label}\n </SelectItem>\n ))}\n </SelectGroup>\n ))}\n </SelectContent>\n </Select>\n {onClearTemplate ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onClearTemplate} disabled={!templateId && !hasEntries}>\n Clear template\n </Button>\n ) : null}\n </div>\n ) : null}\n\n {onImportTemplate || onExportTemplate ? (\n <>\n <Separator />\n <div className=\"text-xs font-medium text-muted-foreground\">Template</div>\n <div className=\"flex flex-col gap-2\">\n {onImportTemplate ? (\n <FilePickerButton label=\"Import template (JSON)\" accept={templateAccept} onPick={onImportTemplate} />\n ) : null}\n {onExportTemplate ? (\n <Button variant=\"outline\" size=\"sm\" disabled={!hasEntries} onClick={onExportTemplate}>\n <Download aria-hidden /> Export template (JSON)\n </Button>\n ) : null}\n </div>\n </>\n ) : null}\n\n {onImportCsv || onExportCsv ? (\n <>\n <Separator />\n <div className=\"text-xs font-medium text-muted-foreground\">Plate map</div>\n <div className=\"flex flex-col gap-2\">\n {onImportCsv ? (\n <FilePickerButton label=\"Import plate map (CSV)\" accept={csvAccept} onPick={onImportCsv} triageCsv />\n ) : null}\n {onExportCsv ? (\n <Button variant=\"outline\" size=\"sm\" disabled={!hasEntries} onClick={onExportCsv}>\n <Download aria-hidden /> Export plate map (CSV)\n </Button>\n ) : null}\n </div>\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["FilePickerButton","label","accept","onPick","disabled","triageCsv","inputRef","React","jsxs","Fragment","Button","jsx","Upload","e","input","f","triage","triagePlateMapCsvFile","TemplateIOPanel","templates","templateId","onTemplateChange","onClearTemplate","hasEntries","onImportCsv","onExportCsv","onImportTemplate","onExportTemplate","className","csvAccept","templateAccept","groups","groupTemplateOptions","cn","Label","Select","v","SelectTrigger","SelectValue","SelectContent","group","opts","SelectGroup","SelectLabel","t","SelectItem","Separator","Download"],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ import { jsxs as l, jsx as e, Fragment as h } from "react/jsx-runtime";
2
+ import { Download as x, Upload as z } from "lucide-react";
3
+ import * as S from "react";
4
+ import { triagePlateMapCsvFile as P } from "./csvPlateTriage.js";
5
+ import { groupTemplateOptions as w } from "./helpers.js";
6
+ import { Button as p } from "../../ui/button.js";
7
+ import { Label as F } from "../../ui/label.js";
8
+ import { Select as O, SelectTrigger as V, SelectValue as j, SelectContent as y, SelectGroup as R, SelectLabel as B, SelectItem as J } from "../../ui/select.js";
9
+ import { Separator as v } from "../../ui/separator.js";
10
+ import { cn as L } from "../../../lib/utils.js";
11
+ function g({ label: n, accept: m, onPick: c, disabled: u, triageCsv: d }) {
12
+ const i = S.useRef(null);
13
+ return /* @__PURE__ */ l(h, { children: [
14
+ /* @__PURE__ */ l(p, { type: "button", variant: "outline", size: "sm", disabled: u, onClick: () => i.current?.click(), children: [
15
+ /* @__PURE__ */ e(z, { "aria-hidden": !0 }),
16
+ " ",
17
+ n
18
+ ] }),
19
+ /* @__PURE__ */ e(
20
+ "input",
21
+ {
22
+ ref: i,
23
+ type: "file",
24
+ accept: m,
25
+ hidden: !0,
26
+ onChange: (a) => {
27
+ const r = a.currentTarget, t = a.target.files?.[0];
28
+ if (!t) {
29
+ r.value = "";
30
+ return;
31
+ }
32
+ (async () => {
33
+ const s = d ? await P(t) : void 0;
34
+ await (s ? c(t, s) : c(t)), r.value = "";
35
+ })();
36
+ }
37
+ }
38
+ )
39
+ ] });
40
+ }
41
+ function K({
42
+ templates: n,
43
+ templateId: m,
44
+ onTemplateChange: c,
45
+ onClearTemplate: u,
46
+ hasEntries: d,
47
+ onImportCsv: i,
48
+ onExportCsv: a,
49
+ onImportTemplate: r,
50
+ onExportTemplate: t,
51
+ className: s,
52
+ csvAccept: N = ".csv,text/csv",
53
+ templateAccept: b = "application/json"
54
+ }) {
55
+ const k = S.useMemo(() => w(n), [n]);
56
+ return /* @__PURE__ */ l("div", { "data-slot": "template-io-panel", className: L("flex flex-col gap-3", s), children: [
57
+ /* @__PURE__ */ e("div", { className: "text-sm font-medium", children: "Template & import / export" }),
58
+ n && c ? /* @__PURE__ */ l("div", { className: "flex flex-col gap-1.5", children: [
59
+ /* @__PURE__ */ e(F, { htmlFor: "plate-template", children: "Template" }),
60
+ /* @__PURE__ */ l(O, { value: m ?? "", onValueChange: (o) => c(o), children: [
61
+ /* @__PURE__ */ e(V, { id: "plate-template", size: "sm", className: "w-full", children: /* @__PURE__ */ e(j, { placeholder: "Select template…" }) }),
62
+ /* @__PURE__ */ e(y, { children: k.map(([o, C]) => /* @__PURE__ */ l(R, { children: [
63
+ o ? /* @__PURE__ */ e(B, { children: o }) : null,
64
+ C.map((f) => /* @__PURE__ */ e(J, { value: f.id, children: f.label }, f.id))
65
+ ] }, o || "_")) })
66
+ ] }),
67
+ u ? /* @__PURE__ */ e(p, { variant: "outline", size: "sm", onClick: u, disabled: !m && !d, children: "Clear template" }) : null
68
+ ] }) : null,
69
+ r || t ? /* @__PURE__ */ l(h, { children: [
70
+ /* @__PURE__ */ e(v, {}),
71
+ /* @__PURE__ */ e("div", { className: "text-xs font-medium text-muted-foreground", children: "Template" }),
72
+ /* @__PURE__ */ l("div", { className: "flex flex-col gap-2", children: [
73
+ r ? /* @__PURE__ */ e(g, { label: "Import template (JSON)", accept: b, onPick: r }) : null,
74
+ t ? /* @__PURE__ */ l(p, { variant: "outline", size: "sm", disabled: !d, onClick: t, children: [
75
+ /* @__PURE__ */ e(x, { "aria-hidden": !0 }),
76
+ " Export template (JSON)"
77
+ ] }) : null
78
+ ] })
79
+ ] }) : null,
80
+ i || a ? /* @__PURE__ */ l(h, { children: [
81
+ /* @__PURE__ */ e(v, {}),
82
+ /* @__PURE__ */ e("div", { className: "text-xs font-medium text-muted-foreground", children: "Plate map" }),
83
+ /* @__PURE__ */ l("div", { className: "flex flex-col gap-2", children: [
84
+ i ? /* @__PURE__ */ e(g, { label: "Import plate map (CSV)", accept: N, onPick: i, triageCsv: !0 }) : null,
85
+ a ? /* @__PURE__ */ l(p, { variant: "outline", size: "sm", disabled: !d, onClick: a, children: [
86
+ /* @__PURE__ */ e(x, { "aria-hidden": !0 }),
87
+ " Export plate map (CSV)"
88
+ ] }) : null
89
+ ] })
90
+ ] }) : null
91
+ ] });
92
+ }
93
+ export {
94
+ K as TemplateIOPanel
95
+ };
96
+ //# sourceMappingURL=TemplateIOPanel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateIOPanel.js","sources":["../../../../src/components/composed/PlateMapEditor/TemplateIOPanel.tsx"],"sourcesContent":["import { Download, Upload } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { triagePlateMapCsvFile } from \"./csvPlateTriage\";\nimport { groupTemplateOptions } from \"./helpers\";\n\nimport type { ImportExportHandlers, PlateMapCsvTriage, TemplateOption } from \"./types\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface TemplateIOPanelProps extends ImportExportHandlers {\n templates?: TemplateOption[];\n templateId?: string;\n onTemplateChange?: (id: string) => void;\n onClearTemplate?: () => void;\n /** Disable export buttons when there's nothing to export. */\n hasEntries?: boolean;\n className?: string;\n csvAccept?: string;\n templateAccept?: string;\n}\n\ninterface FilePickerButtonProps {\n label: string;\n accept: string;\n onPick: (file: File, triage?: PlateMapCsvTriage) => void | Promise<void>;\n disabled?: boolean;\n triageCsv?: boolean;\n}\n\nfunction FilePickerButton({ label, accept, onPick, disabled, triageCsv }: FilePickerButtonProps) {\n const inputRef = React.useRef<HTMLInputElement>(null);\n return (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={disabled} onClick={() => inputRef.current?.click()}>\n <Upload aria-hidden /> {label}\n </Button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n hidden\n onChange={(e) => {\n const input = e.currentTarget;\n const f = e.target.files?.[0];\n if (!f) {\n input.value = \"\";\n return;\n }\n\n void (async () => {\n const triage = triageCsv ? await triagePlateMapCsvFile(f) : undefined;\n await (triage ? onPick(f, triage) : onPick(f));\n input.value = \"\";\n })();\n }}\n />\n </>\n );\n}\n\nexport function TemplateIOPanel({\n templates,\n templateId,\n onTemplateChange,\n onClearTemplate,\n hasEntries,\n onImportCsv,\n onExportCsv,\n onImportTemplate,\n onExportTemplate,\n className,\n csvAccept = \".csv,text/csv\",\n templateAccept = \"application/json\",\n}: TemplateIOPanelProps) {\n const groups = React.useMemo(() => groupTemplateOptions(templates), [templates]);\n\n return (\n <div data-slot=\"template-io-panel\" className={cn(\"flex flex-col gap-3\", className)}>\n <div className=\"text-sm font-medium\">Template &amp; import / export</div>\n\n {templates && onTemplateChange ? (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"plate-template\">Template</Label>\n <Select value={templateId ?? \"\"} onValueChange={(v) => onTemplateChange(v)}>\n <SelectTrigger id=\"plate-template\" size=\"sm\" className=\"w-full\">\n <SelectValue placeholder=\"Select template…\" />\n </SelectTrigger>\n <SelectContent>\n {groups.map(([group, opts]) => (\n <SelectGroup key={group || \"_\"}>\n {group ? <SelectLabel>{group}</SelectLabel> : null}\n {opts.map((t) => (\n <SelectItem key={t.id} value={t.id}>\n {t.label}\n </SelectItem>\n ))}\n </SelectGroup>\n ))}\n </SelectContent>\n </Select>\n {onClearTemplate ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onClearTemplate} disabled={!templateId && !hasEntries}>\n Clear template\n </Button>\n ) : null}\n </div>\n ) : null}\n\n {onImportTemplate || onExportTemplate ? (\n <>\n <Separator />\n <div className=\"text-xs font-medium text-muted-foreground\">Template</div>\n <div className=\"flex flex-col gap-2\">\n {onImportTemplate ? (\n <FilePickerButton label=\"Import template (JSON)\" accept={templateAccept} onPick={onImportTemplate} />\n ) : null}\n {onExportTemplate ? (\n <Button variant=\"outline\" size=\"sm\" disabled={!hasEntries} onClick={onExportTemplate}>\n <Download aria-hidden /> Export template (JSON)\n </Button>\n ) : null}\n </div>\n </>\n ) : null}\n\n {onImportCsv || onExportCsv ? (\n <>\n <Separator />\n <div className=\"text-xs font-medium text-muted-foreground\">Plate map</div>\n <div className=\"flex flex-col gap-2\">\n {onImportCsv ? (\n <FilePickerButton label=\"Import plate map (CSV)\" accept={csvAccept} onPick={onImportCsv} triageCsv />\n ) : null}\n {onExportCsv ? (\n <Button variant=\"outline\" size=\"sm\" disabled={!hasEntries} onClick={onExportCsv}>\n <Download aria-hidden /> Export plate map (CSV)\n </Button>\n ) : null}\n </div>\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["FilePickerButton","label","accept","onPick","disabled","triageCsv","inputRef","React","jsxs","Fragment","Button","jsx","Upload","e","input","f","triage","triagePlateMapCsvFile","TemplateIOPanel","templates","templateId","onTemplateChange","onClearTemplate","hasEntries","onImportCsv","onExportCsv","onImportTemplate","onExportTemplate","className","csvAccept","templateAccept","groups","groupTemplateOptions","cn","Label","Select","v","SelectTrigger","SelectValue","SelectContent","group","opts","SelectGroup","SelectLabel","t","SelectItem","Separator","Download"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),x=require("lucide-react"),g=require("react"),f=require("../../ui/button.cjs"),c=require("../../ui/card.cjs"),s=require("../../../lib/utils.cjs");function b(r){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(r){for(const l in r)if(l!=="default"){const a=Object.getOwnPropertyDescriptor(r,l);Object.defineProperty(n,l,a.get?a:{enumerable:!0,get:()=>r[l]})}}return n.default=r,Object.freeze(n)}const j=b(g);function p({items:r,renderItem:n,onHoverEnter:l,onHoverLeave:a,onRemove:d,removeLabel:i="Remove",emptyLabel:u="No items",className:o}){return r.length===0?t.jsx("div",{className:s.cn("text-xs text-muted-foreground",o),"data-slot":"well-legend-empty",children:u}):t.jsx("div",{className:s.cn("flex flex-col gap-1.5",o),"data-slot":"well-legend",children:r.map(e=>n?t.jsx(j.Fragment,{children:n(e)},e.id):t.jsx(c.Card,{size:"sm","data-slot":"well-legend-item","data-disabled":e.disabled||void 0,className:s.cn("py-2 hover:bg-muted/40",e.disabled&&"opacity-50"),onMouseEnter:()=>l?.(e.id),onMouseLeave:()=>a?.(e.id),children:t.jsxs(c.CardContent,{className:"flex items-start gap-2",children:[t.jsx("span",{"aria-hidden":!0,className:"mt-0.5 size-3.5 shrink-0 rounded-sm border border-foreground/20",style:{backgroundColor:e.color}}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsx("div",{className:"truncate text-xs font-medium",children:e.label}),e.meta?t.jsx("div",{className:"truncate text-[0.65rem] text-muted-foreground",children:e.meta}):null]}),d?t.jsx(f.Button,{type:"button",variant:"ghost",size:"icon-xs","aria-label":`${i} ${typeof e.label=="string"?e.label:e.id}`,onClick:()=>d(e.id),children:t.jsx(x.X,{"aria-hidden":!0})}):null]})},e.id))})}exports.WellLegend=p;
2
+ //# sourceMappingURL=WellLegend.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WellLegend.cjs","sources":["../../../../src/components/composed/PlateMapEditor/WellLegend.tsx"],"sourcesContent":["import { X } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface WellLegendItem {\n id: string;\n label: React.ReactNode;\n color: string;\n /** Optional secondary text shown beneath the label. */\n meta?: React.ReactNode;\n disabled?: boolean;\n}\n\nexport interface WellLegendProps {\n items: WellLegendItem[];\n /** Render-prop for fully custom cards. Bypasses the default card layout. */\n renderItem?: (item: WellLegendItem) => React.ReactNode;\n onHoverEnter?: (id: string) => void;\n onHoverLeave?: (id: string) => void;\n onRemove?: (id: string) => void;\n removeLabel?: string;\n emptyLabel?: string;\n className?: string;\n}\n\nexport function WellLegend({\n items,\n renderItem,\n onHoverEnter,\n onHoverLeave,\n onRemove,\n removeLabel = \"Remove\",\n emptyLabel = \"No items\",\n className,\n}: WellLegendProps) {\n if (items.length === 0) {\n return (\n <div className={cn(\"text-xs text-muted-foreground\", className)} data-slot=\"well-legend-empty\">\n {emptyLabel}\n </div>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-1.5\", className)} data-slot=\"well-legend\">\n {items.map((item) => {\n if (renderItem) return <React.Fragment key={item.id}>{renderItem(item)}</React.Fragment>;\n return (\n <Card\n key={item.id}\n size=\"sm\"\n data-slot=\"well-legend-item\"\n data-disabled={item.disabled || undefined}\n className={cn(\"py-2 hover:bg-muted/40\", item.disabled && \"opacity-50\")}\n onMouseEnter={() => onHoverEnter?.(item.id)}\n onMouseLeave={() => onHoverLeave?.(item.id)}\n >\n <CardContent className=\"flex items-start gap-2\">\n <span\n aria-hidden\n className=\"mt-0.5 size-3.5 shrink-0 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: item.color }}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-xs font-medium\">{item.label}</div>\n {item.meta ? (\n <div className=\"truncate text-[0.65rem] text-muted-foreground\">{item.meta}</div>\n ) : null}\n </div>\n {onRemove ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={`${removeLabel} ${typeof item.label === \"string\" ? item.label : item.id}`}\n onClick={() => onRemove(item.id)}\n >\n <X aria-hidden />\n </Button>\n ) : null}\n </CardContent>\n </Card>\n );\n })}\n </div>\n );\n}\n"],"names":["WellLegend","items","renderItem","onHoverEnter","onHoverLeave","onRemove","removeLabel","emptyLabel","className","jsx","cn","item","React","Card","jsxs","CardContent","Button","X"],"mappings":"giBA4BO,SAASA,EAAW,CACzB,MAAAC,EACA,WAAAC,EACA,aAAAC,EACA,aAAAC,EACA,SAAAC,EACA,YAAAC,EAAc,SACd,WAAAC,EAAa,WACb,UAAAC,CACF,EAAoB,CAClB,OAAIP,EAAM,SAAW,EAEjBQ,MAAC,OAAI,UAAWC,EAAAA,GAAG,gCAAiCF,CAAS,EAAG,YAAU,oBACvE,SAAAD,CAAA,CACH,EAKFE,EAAAA,IAAC,MAAA,CAAI,UAAWC,EAAAA,GAAG,wBAAyBF,CAAS,EAAG,YAAU,cAC/D,SAAAP,EAAM,IAAKU,GACNT,EAAmBO,MAACG,EAAM,SAAN,CAA8B,SAAAV,EAAWS,CAAI,GAAzBA,EAAK,EAAsB,EAErEF,EAAAA,IAACI,EAAAA,KAAA,CAEC,KAAK,KACL,YAAU,mBACV,gBAAeF,EAAK,UAAY,OAChC,UAAWD,EAAAA,GAAG,yBAA0BC,EAAK,UAAY,YAAY,EACrE,aAAc,IAAMR,IAAeQ,EAAK,EAAE,EAC1C,aAAc,IAAMP,IAAeO,EAAK,EAAE,EAE1C,SAAAG,EAAAA,KAACC,EAAAA,YAAA,CAAY,UAAU,yBACrB,SAAA,CAAAN,EAAAA,IAAC,OAAA,CACC,cAAW,GACX,UAAU,kEACV,MAAO,CAAE,gBAAiBE,EAAK,KAAA,CAAM,CAAA,EAEvCG,EAAAA,KAAC,MAAA,CAAI,UAAU,iBACb,SAAA,CAAAL,EAAAA,IAAC,MAAA,CAAI,UAAU,+BAAgC,SAAAE,EAAK,MAAM,EACzDA,EAAK,KACJF,MAAC,MAAA,CAAI,UAAU,gDAAiD,SAAAE,EAAK,KAAK,EACxE,IAAA,EACN,EACCN,EACCI,EAAAA,IAACO,EAAAA,OAAA,CACC,KAAK,SACL,QAAQ,QACR,KAAK,UACL,aAAY,GAAGV,CAAW,IAAI,OAAOK,EAAK,OAAU,SAAWA,EAAK,MAAQA,EAAK,EAAE,GACnF,QAAS,IAAMN,EAASM,EAAK,EAAE,EAE/B,SAAAF,EAAAA,IAACQ,EAAAA,EAAA,CAAE,cAAW,EAAA,CAAC,CAAA,CAAA,EAEf,IAAA,CAAA,CACN,CAAA,EA/BKN,EAAK,EAAA,CAkCf,CAAA,CACH,CAEJ"}
@@ -0,0 +1,58 @@
1
+ import { jsx as r, jsxs as n } from "react/jsx-runtime";
2
+ import { X as m } from "lucide-react";
3
+ import * as f from "react";
4
+ import { Button as p } from "../../ui/button.js";
5
+ import { Card as g, CardContent as h } from "../../ui/card.js";
6
+ import { cn as l } from "../../../lib/utils.js";
7
+ function C({
8
+ items: a,
9
+ renderItem: d,
10
+ onHoverEnter: s,
11
+ onHoverLeave: i,
12
+ onRemove: t,
13
+ removeLabel: c = "Remove",
14
+ emptyLabel: u = "No items",
15
+ className: o
16
+ }) {
17
+ return a.length === 0 ? /* @__PURE__ */ r("div", { className: l("text-xs text-muted-foreground", o), "data-slot": "well-legend-empty", children: u }) : /* @__PURE__ */ r("div", { className: l("flex flex-col gap-1.5", o), "data-slot": "well-legend", children: a.map((e) => d ? /* @__PURE__ */ r(f.Fragment, { children: d(e) }, e.id) : /* @__PURE__ */ r(
18
+ g,
19
+ {
20
+ size: "sm",
21
+ "data-slot": "well-legend-item",
22
+ "data-disabled": e.disabled || void 0,
23
+ className: l("py-2 hover:bg-muted/40", e.disabled && "opacity-50"),
24
+ onMouseEnter: () => s?.(e.id),
25
+ onMouseLeave: () => i?.(e.id),
26
+ children: /* @__PURE__ */ n(h, { className: "flex items-start gap-2", children: [
27
+ /* @__PURE__ */ r(
28
+ "span",
29
+ {
30
+ "aria-hidden": !0,
31
+ className: "mt-0.5 size-3.5 shrink-0 rounded-sm border border-foreground/20",
32
+ style: { backgroundColor: e.color }
33
+ }
34
+ ),
35
+ /* @__PURE__ */ n("div", { className: "min-w-0 flex-1", children: [
36
+ /* @__PURE__ */ r("div", { className: "truncate text-xs font-medium", children: e.label }),
37
+ e.meta ? /* @__PURE__ */ r("div", { className: "truncate text-[0.65rem] text-muted-foreground", children: e.meta }) : null
38
+ ] }),
39
+ t ? /* @__PURE__ */ r(
40
+ p,
41
+ {
42
+ type: "button",
43
+ variant: "ghost",
44
+ size: "icon-xs",
45
+ "aria-label": `${c} ${typeof e.label == "string" ? e.label : e.id}`,
46
+ onClick: () => t(e.id),
47
+ children: /* @__PURE__ */ r(m, { "aria-hidden": !0 })
48
+ }
49
+ ) : null
50
+ ] })
51
+ },
52
+ e.id
53
+ )) });
54
+ }
55
+ export {
56
+ C as WellLegend
57
+ };
58
+ //# sourceMappingURL=WellLegend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WellLegend.js","sources":["../../../../src/components/composed/PlateMapEditor/WellLegend.tsx"],"sourcesContent":["import { X } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface WellLegendItem {\n id: string;\n label: React.ReactNode;\n color: string;\n /** Optional secondary text shown beneath the label. */\n meta?: React.ReactNode;\n disabled?: boolean;\n}\n\nexport interface WellLegendProps {\n items: WellLegendItem[];\n /** Render-prop for fully custom cards. Bypasses the default card layout. */\n renderItem?: (item: WellLegendItem) => React.ReactNode;\n onHoverEnter?: (id: string) => void;\n onHoverLeave?: (id: string) => void;\n onRemove?: (id: string) => void;\n removeLabel?: string;\n emptyLabel?: string;\n className?: string;\n}\n\nexport function WellLegend({\n items,\n renderItem,\n onHoverEnter,\n onHoverLeave,\n onRemove,\n removeLabel = \"Remove\",\n emptyLabel = \"No items\",\n className,\n}: WellLegendProps) {\n if (items.length === 0) {\n return (\n <div className={cn(\"text-xs text-muted-foreground\", className)} data-slot=\"well-legend-empty\">\n {emptyLabel}\n </div>\n );\n }\n\n return (\n <div className={cn(\"flex flex-col gap-1.5\", className)} data-slot=\"well-legend\">\n {items.map((item) => {\n if (renderItem) return <React.Fragment key={item.id}>{renderItem(item)}</React.Fragment>;\n return (\n <Card\n key={item.id}\n size=\"sm\"\n data-slot=\"well-legend-item\"\n data-disabled={item.disabled || undefined}\n className={cn(\"py-2 hover:bg-muted/40\", item.disabled && \"opacity-50\")}\n onMouseEnter={() => onHoverEnter?.(item.id)}\n onMouseLeave={() => onHoverLeave?.(item.id)}\n >\n <CardContent className=\"flex items-start gap-2\">\n <span\n aria-hidden\n className=\"mt-0.5 size-3.5 shrink-0 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: item.color }}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-xs font-medium\">{item.label}</div>\n {item.meta ? (\n <div className=\"truncate text-[0.65rem] text-muted-foreground\">{item.meta}</div>\n ) : null}\n </div>\n {onRemove ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={`${removeLabel} ${typeof item.label === \"string\" ? item.label : item.id}`}\n onClick={() => onRemove(item.id)}\n >\n <X aria-hidden />\n </Button>\n ) : null}\n </CardContent>\n </Card>\n );\n })}\n </div>\n );\n}\n"],"names":["WellLegend","items","renderItem","onHoverEnter","onHoverLeave","onRemove","removeLabel","emptyLabel","className","jsx","cn","item","React","Card","jsxs","CardContent","Button","X"],"mappings":";;;;;;AA4BO,SAASA,EAAW;AAAA,EACzB,OAAAC;AAAA,EACA,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,UAAAC;AAAA,EACA,aAAAC,IAAc;AAAA,EACd,YAAAC,IAAa;AAAA,EACb,WAAAC;AACF,GAAoB;AAClB,SAAIP,EAAM,WAAW,IAEjB,gBAAAQ,EAAC,SAAI,WAAWC,EAAG,iCAAiCF,CAAS,GAAG,aAAU,qBACvE,UAAAD,EAAA,CACH,IAKF,gBAAAE,EAAC,OAAA,EAAI,WAAWC,EAAG,yBAAyBF,CAAS,GAAG,aAAU,eAC/D,UAAAP,EAAM,IAAI,CAACU,MACNT,IAAmB,gBAAAO,EAACG,EAAM,UAAN,EAA8B,UAAAV,EAAWS,CAAI,KAAzBA,EAAK,EAAsB,IAErE,gBAAAF;AAAA,IAACI;AAAA,IAAA;AAAA,MAEC,MAAK;AAAA,MACL,aAAU;AAAA,MACV,iBAAeF,EAAK,YAAY;AAAA,MAChC,WAAWD,EAAG,0BAA0BC,EAAK,YAAY,YAAY;AAAA,MACrE,cAAc,MAAMR,IAAeQ,EAAK,EAAE;AAAA,MAC1C,cAAc,MAAMP,IAAeO,EAAK,EAAE;AAAA,MAE1C,UAAA,gBAAAG,EAACC,GAAA,EAAY,WAAU,0BACrB,UAAA;AAAA,QAAA,gBAAAN;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,eAAW;AAAA,YACX,WAAU;AAAA,YACV,OAAO,EAAE,iBAAiBE,EAAK,MAAA;AAAA,UAAM;AAAA,QAAA;AAAA,QAEvC,gBAAAG,EAAC,OAAA,EAAI,WAAU,kBACb,UAAA;AAAA,UAAA,gBAAAL,EAAC,OAAA,EAAI,WAAU,gCAAgC,UAAAE,EAAK,OAAM;AAAA,UACzDA,EAAK,OACJ,gBAAAF,EAAC,OAAA,EAAI,WAAU,iDAAiD,UAAAE,EAAK,MAAK,IACxE;AAAA,QAAA,GACN;AAAA,QACCN,IACC,gBAAAI;AAAA,UAACO;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,cAAY,GAAGV,CAAW,IAAI,OAAOK,EAAK,SAAU,WAAWA,EAAK,QAAQA,EAAK,EAAE;AAAA,YACnF,SAAS,MAAMN,EAASM,EAAK,EAAE;AAAA,YAE/B,UAAA,gBAAAF,EAACQ,GAAA,EAAE,eAAW,GAAA,CAAC;AAAA,UAAA;AAAA,QAAA,IAEf;AAAA,MAAA,EAAA,CACN;AAAA,IAAA;AAAA,IA/BKN,EAAK;AAAA,EAAA,CAkCf,EAAA,CACH;AAEJ;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("react/jsx-runtime"),T=require("lucide-react"),we=require("react"),ve=require("./ManifestFilterPopover.cjs"),U=require("../../ui/badge.cjs"),F=require("../../ui/button.cjs"),Y=require("../../ui/checkbox.cjs"),b=require("../../ui/combobox.cjs"),Ne=require("../../ui/data-table/data-table.cjs"),ye=require("../../ui/input.cjs"),h=require("../../ui/select.cjs"),Te=require("../../ui/switch.cjs"),f=require("../../ui/table.cjs"),R=require("../../ui/tooltip.cjs"),P=require("../../../lib/utils.cjs");function ke(o){const i=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(o){for(const c in o)if(c!=="default"){const d=Object.getOwnPropertyDescriptor(o,c);Object.defineProperty(i,c,d.get?d:{enumerable:!0,get:()=>o[c]})}}return i.default=o,Object.freeze(i)}const m=ke(we);function _e({ariaLabel:o,value:i,options:c,placeholder:d,onChange:p}){const j=b.useComboboxAnchor(),g=m.useMemo(()=>{const x=new Map;return c.forEach(C=>x.set(C.value,C.label)),x},[c]);return t.jsxs(b.Combobox,{multiple:!0,items:c.map(x=>x.value),value:i,onValueChange:p,children:[t.jsxs(b.ComboboxChips,{ref:j,className:P.cn("min-h-7 w-full py-0.5",B),children:[t.jsx(b.ComboboxValue,{children:x=>x.map(C=>t.jsx(b.ComboboxChip,{children:g.get(C)??C},C))}),t.jsx(b.ComboboxChipsInput,{"aria-label":o,placeholder:d??"Select…"})]}),t.jsxs(b.ComboboxContent,{anchor:j,children:[t.jsx(b.ComboboxEmpty,{children:"No options."}),t.jsx(b.ComboboxList,{children:x=>t.jsx(b.ComboboxItem,{value:x,children:g.get(x)??x},x)})]})]})}const B="border-transparent shadow-none hover:border-input/40",J=25,Me=50,Ee=100,ze=J,Fe=[J,Me,Ee];function K(o){return o!=null&&o!==""}const Re={number:"number",integer:"number",date:"date",datetime:"datetime-local",time:"time"};function Pe(o){return Re[o]??"text"}function Ae(o,i){if(i!==""){if(o==="number"){const c=parseFloat(i);return Number.isFinite(c)?c:i}if(o==="integer"){const c=parseInt(i,10);return Number.isFinite(c)?c:i}return i}}function Ie(o,i){const c=new Map;for(const d of o){const p=d.row[i],j=p==null||p===""?"(blank)":String(p),g=c.get(j);g?g.rows.push(d):c.set(j,{key:j,rows:[d]})}return[...c.values()]}function qe(o){return o.filter(i=>!!i.field).map(i=>({columnId:i.field,label:i.header}))}function Be({values:o,columns:i,fields:c,selection:d,onSelectionChange:p,onChange:j,emptyEntry:g,isPopulated:x,pageSize:C=ze,pageSizeOptions:Q=Fe,enableFillDown:D=!0,rowProps:X,filterable:A=!1,filterColumns:G,groupable:L=!1,className:ee}){const[z,te]=m.useState(!1),[w,k]=m.useState(0),[S,ne]=m.useState(C),[_,se]=m.useState([]),[I,re]=m.useState(""),[le,ae]=m.useState(new Set);m.useEffect(()=>{k(0)},[z]);const V=m.useMemo(()=>{const e=new Map;return(c??[]).forEach(n=>e.set(n.key,n)),e},[c]),ie=m.useMemo(()=>G??qe(i),[i,G]),v=m.useMemo(()=>{const e=[...o.entries()].map(([s,r])=>({id:s,row:r}));let n=e;if(!z&&x&&(n=e.filter(s=>x(s.row))),A&&_.length>0&&(n=n.filter(s=>{const r=s.row;return _.every(l=>{const a=r[l.columnId],u=a==null?"":String(a);return Ne.applyFilterCondition(u,l.operator,l.value)})})),d&&d.size>0){const s=new Set(n.map(r=>r.id));d.forEach(r=>{!s.has(r)&&!o.has(r)&&(n=[...n,{id:r,row:g(r)}])})}return n.sort((s,r)=>s.id.localeCompare(r.id))},[o,z,x,d,g,A,_]);m.useEffect(()=>{k(0)},[_]);const q=m.useMemo(()=>v.slice(w*S,w*S+S),[v,w,S]),N=(e,n)=>{const s=new Map(o),r=s.get(e)??g(e);s.set(e,{...r,...n}),j(s)},oe=e=>{if(!p)return;const n=new Set(d??[]);n.has(e)?n.delete(e):n.add(e),p(n)},W=e=>{if(!e.field)return null;const n=!!d&&d.size>0,s=n?v.filter(({id:u})=>d.has(u)):q,r=s.findIndex(({row:u})=>K(u[e.field]));if(r<0)return null;const l=s[r],a=n?s.filter(({id:u})=>u!==l.id):s.slice(r+1);return{field:e.field,source:l,targets:a,value:l.row[e.field]}},ce=e=>{const n=W(e);if(!n||n.targets.length===0)return;const s=new Map(o);n.targets.forEach(({id:r})=>{const l=s.get(r)??g(r);s.set(r,{...l,[n.field]:n.value})}),j(s)},de=(e,n,s,r,l)=>t.jsxs(h.Select,{value:s??"",onValueChange:a=>N(r,{[n]:a}),children:[t.jsx(h.SelectTrigger,{size:"sm",className:P.cn("h-7 w-full",B),"aria-label":l,children:t.jsx(h.SelectValue,{placeholder:e.placeholder??"Select…"})}),t.jsx(h.SelectContent,{children:(e.options??[]).map(a=>t.jsx(h.SelectItem,{value:a.value,children:t.jsxs("span",{className:"inline-flex items-center gap-2",children:[a.swatch?t.jsx("span",{className:"inline-block h-2.5 w-2.5 rounded-sm border border-foreground/20",style:{backgroundColor:a.swatch},"aria-hidden":!0}):null,a.label]})},a.value))})]}),ue=(e,n,s,r,l)=>{const a=!!s;return e.boolStyle==="switch"?t.jsx(Te.Switch,{"aria-label":l,checked:a,onCheckedChange:u=>N(r,{[n]:u})}):t.jsx(Y.Checkbox,{"aria-label":l,checked:a,onCheckedChange:u=>N(r,{[n]:u===!0})})},he=(e,n,s,r,l)=>t.jsx(ye.Input,{type:Pe(e.kind),step:e.kind==="integer"?1:void 0,placeholder:e.placeholder,"aria-label":l,className:P.cn("h-7",B),value:s??"",onChange:a=>N(r,{[n]:Ae(e.kind,a.target.value)})}),me=(e,n,s,r)=>{const l=n.field,a=r[l],u=`${e.label} for ${s}`;if(e.kind==="select")return de(e,l,a,s,u);if(e.kind==="multiselect"){const Se=Array.isArray(a)?a:[];return t.jsx(_e,{ariaLabel:u,value:Se,options:e.options??[],placeholder:e.placeholder,onChange:Z=>N(s,{[l]:Z.length===0?void 0:Z})})}return e.kind==="boolean"?ue(e,l,a,s,u):he(e,l,a,s,u)},xe=(e,n)=>{if(e?.kind==="select"){const s=(e.options??[]).find(r=>r.value===n);return s?t.jsxs(U.Badge,{variant:"secondary",children:[s.swatch?t.jsx("span",{className:"size-2 rounded-sm border border-foreground/20",style:{backgroundColor:s.swatch},"aria-hidden":!0}):null,s.label]}):t.jsx("span",{className:"text-muted-foreground",children:"—"})}if(e?.kind==="multiselect"){const s=Array.isArray(n)?n:[];return s.length===0?t.jsx("span",{className:"text-muted-foreground",children:"—"}):t.jsx("div",{className:"flex flex-wrap gap-1",children:s.map(r=>{const l=(e.options??[]).find(a=>a.value===r);return t.jsxs(U.Badge,{variant:"secondary",children:[l?.swatch?t.jsx("span",{className:"size-2 rounded-sm border border-foreground/20",style:{backgroundColor:l.swatch},"aria-hidden":!0}):null,l?.label??r]},r)})})}return e?.kind==="boolean"?n?t.jsx(T.Check,{"aria-label":"Yes",className:"size-4 text-foreground"}):t.jsx("span",{className:"text-muted-foreground",children:"—"}):K(n)?String(n):t.jsx("span",{className:"text-muted-foreground",children:"—"})},fe=(e,n,s)=>{if(e.render)return e.render({row:s,wellId:n,update:a=>N(n,a)});if(!e.field)return null;const r=V.get(e.field),l=s[e.field];return r?.editableInTable&&r.kind!=="custom"?me(r,e,n,s):xe(r,l)},pe=e=>{const n=e.field?V.get(e.field):void 0,s=e.icon??n?.icon,r=D?W(e):null,l=!!r&&r.targets.length>0,a=d&&d.size>0?`Fill selected ${e.header} cells from the first selected value`:`Fill visible ${e.header} cells downward from the first value`;return t.jsxs("span",{className:"flex min-w-0 items-center justify-between gap-2",children:[t.jsxs("span",{className:"inline-flex min-w-0 items-center gap-1.5",children:[s?t.jsx("span",{className:"inline-flex text-muted-foreground [&_svg]:size-3.5",children:s}):null,t.jsx("span",{className:"truncate",children:e.header})]}),D&&e.field?t.jsxs(R.Tooltip,{children:[t.jsx(R.TooltipTrigger,{asChild:!0,children:t.jsx(F.Button,{type:"button",variant:"ghost",size:"icon-xs",className:"-mr-1 text-muted-foreground hover:text-foreground",disabled:!l,"aria-label":`Fill down ${e.header}`,onClick:u=>{u.preventDefault(),u.stopPropagation(),ce(e)},children:t.jsx(T.ArrowDownToLine,{})})}),t.jsx(R.TooltipContent,{children:a})]}):null]})},ge=m.useMemo(()=>i.filter(e=>!!e.field),[i]),y=L&&I?I:"",M=m.useMemo(()=>y?Ie(v,y):null,[y,v]),E=v.length,O=Math.max(0,Math.ceil(E/S)-1),be=d?.size??0,$=i.length+1+(p?1:0),H=(e,n)=>{const s=d?.has(e),r=X?.({wellId:e,row:n,isSelected:!!s});return t.jsxs(f.TableRow,{...r,children:[p?t.jsx(f.TableCell,{className:"w-10",children:t.jsx(Y.Checkbox,{checked:!!s,onCheckedChange:()=>oe(e),"aria-label":`Select ${e}`})}):null,t.jsx(f.TableCell,{className:"font-semibold",children:e}),i.map(l=>t.jsx(f.TableCell,{style:l.minWidth?{minWidth:l.minWidth}:void 0,children:fe(l,e,n)},l.id??l.field??l.header))]},e)},je=e=>{ae(n=>{const s=new Set(n);return s.has(e)?s.delete(e):s.add(e),s})},Ce=y?i.find(n=>n.field===y)?.header??y:"";return t.jsxs("div",{"data-slot":"well-manifest-table",className:P.cn("flex flex-col gap-2",ee),children:[t.jsxs("div",{className:"flex flex-wrap items-center gap-2",children:[t.jsx(F.Button,{variant:"outline",size:"sm",onClick:()=>te(e=>!e),children:z?"Hide empty wells":"Show all wells"}),A?t.jsx(ve.ManifestFilterPopover,{columns:ie,filters:_,onFiltersChange:se}):null,L?t.jsxs("div",{className:"inline-flex items-center gap-1.5",children:[t.jsx(T.Layers,{"aria-hidden":!0,className:"size-3.5 text-muted-foreground"}),t.jsxs(h.Select,{value:I||"__none",onValueChange:e=>re(e==="__none"?"":e),children:[t.jsx(h.SelectTrigger,{size:"sm",className:"h-7 min-w-40","aria-label":"Group by",children:t.jsx(h.SelectValue,{placeholder:"Group by…"})}),t.jsxs(h.SelectContent,{children:[t.jsx(h.SelectItem,{value:"__none",children:"No grouping"}),ge.map(e=>t.jsx(h.SelectItem,{value:e.field??"",children:e.header},e.id??e.field))]})]})]}):null,t.jsxs("span",{className:"text-xs text-muted-foreground",children:[E," rows · ",be," selected"]})]}),t.jsx(R.TooltipProvider,{children:t.jsxs(f.Table,{"data-density":"default",children:[t.jsx(f.TableHeader,{children:t.jsxs(f.TableRow,{children:[p?t.jsx(f.TableHead,{className:"w-10 text-center",children:t.jsxs("span",{className:"inline-flex items-center justify-center text-muted-foreground [&_svg]:size-3.5",children:[t.jsx(T.Check,{"aria-hidden":!0}),t.jsx("span",{className:"sr-only",children:"Selected"})]})}):null,t.jsx(f.TableHead,{style:{minWidth:60},children:"Well"}),i.map(e=>t.jsx(f.TableHead,{style:e.minWidth?{minWidth:e.minWidth}:void 0,children:pe(e)},e.id??e.field??e.header))]})}),t.jsxs(f.TableBody,{children:[M?M.map(e=>{const n=le.has(e.key);return t.jsxs(m.Fragment,{children:[t.jsx(f.TableRow,{className:"cursor-pointer bg-muted/40",onClick:()=>je(e.key),children:t.jsx(f.TableCell,{colSpan:$,className:"py-1.5",children:t.jsxs("div",{className:"flex items-center gap-2 text-xs font-medium",children:[n?t.jsx(T.ChevronRight,{"aria-hidden":!0,className:"size-3.5 text-muted-foreground"}):t.jsx(T.ChevronDown,{"aria-hidden":!0,className:"size-3.5 text-muted-foreground"}),t.jsxs("span",{children:[Ce,": ",e.key]}),t.jsxs("span",{className:"text-muted-foreground",children:["(",e.rows.length," ",e.rows.length===1?"row":"rows",")"]})]})})}),n?null:e.rows.map(({id:s,row:r})=>H(s,r))]},e.key)}):q.map(({id:e,row:n})=>H(e,n)),(M?M.length===0:q.length===0)?t.jsx(f.TableRow,{children:t.jsx(f.TableCell,{colSpan:$,className:"text-xs text-muted-foreground",children:"No rows. Paint wells on the plate."})}):null]})]})}),M?null:t.jsxs("div",{className:"flex items-center justify-end gap-3 text-xs text-muted-foreground",children:[t.jsx("span",{className:"text-muted-foreground",children:"Rows per page"}),t.jsxs(h.Select,{value:String(S),onValueChange:e=>{ne(parseInt(e,10)),k(0)},children:[t.jsx(h.SelectTrigger,{size:"sm",className:"h-7 w-18","aria-label":"Rows per page",children:t.jsx(h.SelectValue,{})}),t.jsx(h.SelectContent,{children:Q.map(e=>t.jsx(h.SelectItem,{value:String(e),children:e},e))})]}),t.jsx("span",{children:E===0?"0 of 0":`${w*S+1}–${Math.min((w+1)*S,E)} of ${E}`}),t.jsx(F.Button,{variant:"outline",size:"sm",disabled:w===0,onClick:()=>k(e=>Math.max(0,e-1)),children:"Prev"}),t.jsx(F.Button,{variant:"outline",size:"sm",disabled:w>=O,onClick:()=>k(e=>Math.min(O,e+1)),children:"Next"})]})]})}exports.WellManifestTable=Be;
2
+ //# sourceMappingURL=WellManifestTable.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WellManifestTable.cjs","sources":["../../../../src/components/composed/PlateMapEditor/WellManifestTable.tsx"],"sourcesContent":["import { ArrowDownToLine, Check, ChevronDown, ChevronRight, Layers } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { ManifestFilterPopover } from \"./ManifestFilterPopover\";\n\nimport type { WellColumn, WellField, WellId, WellRecord, WellSelectOption } from \"./types\";\nimport type { FilterColumnConfig, FilterCondition } from \"@/components/ui/data-table/data-table\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n Combobox,\n ComboboxChip,\n ComboboxChips,\n ComboboxChipsInput,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxItem,\n ComboboxList,\n ComboboxValue,\n useComboboxAnchor,\n} from \"@/components/ui/combobox\";\nimport { applyFilterCondition } from \"@/components/ui/data-table/data-table\";\nimport { Input } from \"@/components/ui/input\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from \"@/components/ui/table\";\nimport { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from \"@/components/ui/tooltip\";\nimport { cn } from \"@/lib/utils\";\n\nfunction MultiSelectCell({\n ariaLabel,\n value,\n options,\n placeholder,\n onChange,\n}: {\n ariaLabel: string;\n value: string[];\n options: WellSelectOption[];\n placeholder?: string;\n onChange: (next: string[]) => void;\n}) {\n const anchorRef = useComboboxAnchor();\n const labelByValue = React.useMemo(() => {\n const m = new Map<string, string>();\n options.forEach((o) => m.set(o.value, o.label));\n return m;\n }, [options]);\n\n return (\n <Combobox multiple items={options.map((o) => o.value)} value={value} onValueChange={onChange}>\n <ComboboxChips ref={anchorRef} className={cn(\"min-h-7 w-full py-0.5\", CELL_EDITOR_CLASS)}>\n <ComboboxValue>\n {(items: string[]) =>\n items.map((item) => <ComboboxChip key={item}>{labelByValue.get(item) ?? item}</ComboboxChip>)\n }\n </ComboboxValue>\n <ComboboxChipsInput aria-label={ariaLabel} placeholder={placeholder ?? \"Select…\"} />\n </ComboboxChips>\n <ComboboxContent anchor={anchorRef}>\n <ComboboxEmpty>No options.</ComboboxEmpty>\n <ComboboxList>\n {(item: string) => (\n <ComboboxItem key={item} value={item}>\n {labelByValue.get(item) ?? item}\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n );\n}\n\n/**\n * Cell editors render borderless by default so the table reads flat. The\n * underlying primitives keep their `focus-visible` / `focus-within` rings, so\n * an outline only appears while the user is actively editing the cell.\n */\nconst CELL_EDITOR_CLASS = \"border-transparent shadow-none hover:border-input/40\";\n\nconst PAGE_SIZE_SMALL = 25;\nconst PAGE_SIZE_MEDIUM = 50;\nconst PAGE_SIZE_LARGE = 100;\nconst DEFAULT_PAGE_SIZE = PAGE_SIZE_SMALL;\nconst DEFAULT_PAGE_SIZE_OPTIONS: number[] = [PAGE_SIZE_SMALL, PAGE_SIZE_MEDIUM, PAGE_SIZE_LARGE];\n\nfunction hasFillValue(value: unknown): boolean {\n return value !== undefined && value !== null && value !== \"\";\n}\n\nconst INPUT_TYPE_BY_KIND: Record<string, string> = {\n number: \"number\",\n integer: \"number\",\n date: \"date\",\n datetime: \"datetime-local\",\n time: \"time\",\n};\n\nfunction resolveInputType(kind: string): string {\n return INPUT_TYPE_BY_KIND[kind] ?? \"text\";\n}\n\nfunction parseInputValue(kind: string, raw: string): unknown {\n if (raw === \"\") return undefined;\n if (kind === \"number\") {\n const num = parseFloat(raw);\n return Number.isFinite(num) ? num : raw;\n }\n if (kind === \"integer\") {\n const num = parseInt(raw, 10);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\nexport interface WellManifestTableRowContext<T extends WellRecord = WellRecord> {\n wellId: WellId;\n row: T;\n isSelected: boolean;\n}\n\nexport interface WellManifestTableProps<T extends WellRecord = WellRecord> {\n values: Map<WellId, T>;\n columns: WellColumn<T>[];\n /**\n * Field schema. If a column has `field` matching a `select`-kind field, a\n * Select cell renders automatically with that field's options.\n */\n fields?: WellField<T>[];\n selection?: Set<WellId>;\n onSelectionChange?: (next: Set<WellId>) => void;\n onChange: (next: Map<WellId, T>) => void;\n /** Builds an empty record for a freshly-created row. */\n emptyEntry: (id: WellId) => T;\n /** Filter that controls which empty rows surface. Defaults to `false`. */\n isPopulated?: (row: T) => boolean;\n pageSize?: number;\n pageSizeOptions?: number[];\n /** Adds a column header action that copies the first non-empty value downward. */\n enableFillDown?: boolean;\n /**\n * Extra props spread onto each `<tr>` — typically used to attach a DnD\n * library's `setNodeRef`, listeners, and data attributes so rows can act as\n * drag sources. The kit stays DnD-library-agnostic.\n */\n rowProps?: (ctx: WellManifestTableRowContext<T>) => React.ComponentProps<\"tr\"> | undefined;\n /** Enables an inline filter popover above the table. Defaults to false. */\n filterable?: boolean;\n /**\n * Optional override of filterable column configs. When omitted, every column\n * with a `field` becomes filterable using the default operator set from the\n * shared data-table filter system.\n */\n filterColumns?: FilterColumnConfig[];\n /** Enables an inline group-by selector. Defaults to false. */\n groupable?: boolean;\n className?: string;\n}\n\ninterface GroupedRow<T> {\n key: string;\n rows: Array<{ id: WellId; row: T }>;\n}\n\nfunction groupRowsBy<T extends WellRecord>(rows: Array<{ id: WellId; row: T }>, field: string): GroupedRow<T>[] {\n const map = new Map<string, GroupedRow<T>>();\n for (const entry of rows) {\n const raw = (entry.row as Record<string, unknown>)[field];\n const key = raw === undefined || raw === null || raw === \"\" ? \"(blank)\" : String(raw);\n const existing = map.get(key);\n if (existing) existing.rows.push(entry);\n else map.set(key, { key, rows: [entry] });\n }\n return [...map.values()];\n}\n\nfunction inferFilterColumns<T extends WellRecord>(columns: WellColumn<T>[]): FilterColumnConfig[] {\n return columns.filter((col) => !!col.field).map((col) => ({ columnId: col.field as string, label: col.header }));\n}\n\nexport function WellManifestTable<T extends WellRecord = WellRecord>({\n values,\n columns,\n fields,\n selection,\n onSelectionChange,\n onChange,\n emptyEntry,\n isPopulated,\n pageSize: initialPageSize = DEFAULT_PAGE_SIZE,\n pageSizeOptions = DEFAULT_PAGE_SIZE_OPTIONS,\n enableFillDown = true,\n rowProps,\n filterable = false,\n filterColumns,\n groupable = false,\n className,\n}: WellManifestTableProps<T>) {\n const [showAll, setShowAll] = React.useState(false);\n const [page, setPage] = React.useState(0);\n const [pageSize, setPageSize] = React.useState(initialPageSize);\n const [filters, setFilters] = React.useState<FilterCondition[]>([]);\n const [groupByField, setGroupByField] = React.useState<string>(\"\");\n const [collapsedGroups, setCollapsedGroups] = React.useState<Set<string>>(new Set());\n\n React.useEffect(() => {\n setPage(0);\n }, [showAll]);\n\n const fieldByKey = React.useMemo(() => {\n const m = new Map<string, WellField<T>>();\n (fields ?? []).forEach((f) => m.set(f.key, f));\n return m;\n }, [fields]);\n\n const resolvedFilterColumns = React.useMemo<FilterColumnConfig[]>(\n () => filterColumns ?? inferFilterColumns(columns),\n [columns, filterColumns],\n );\n\n const rows = React.useMemo(() => {\n const arr = [...values.entries()].map(([id, row]) => ({\n id,\n row,\n }));\n let filtered = arr;\n if (!showAll && isPopulated) {\n filtered = arr.filter((r) => isPopulated(r.row));\n }\n if (filterable && filters.length > 0) {\n filtered = filtered.filter((entry) => {\n const record = entry.row as Record<string, unknown>;\n return filters.every((condition) => {\n const cellRaw = record[condition.columnId];\n const cellValue = cellRaw === undefined || cellRaw === null ? \"\" : String(cellRaw);\n return applyFilterCondition(cellValue, condition.operator, condition.value);\n });\n });\n }\n if (selection && selection.size > 0) {\n const have = new Set(filtered.map((r) => r.id));\n selection.forEach((p) => {\n if (!have.has(p) && !values.has(p)) {\n filtered = [...filtered, { id: p, row: emptyEntry(p) }];\n }\n });\n }\n return filtered.sort((a, b) => a.id.localeCompare(b.id));\n }, [values, showAll, isPopulated, selection, emptyEntry, filterable, filters]);\n\n React.useEffect(() => {\n setPage(0);\n }, [filters]);\n\n const pagedRows = React.useMemo(\n () => rows.slice(page * pageSize, page * pageSize + pageSize),\n [rows, page, pageSize],\n );\n\n const updateRow = (wellId: WellId, patch: Partial<T>) => {\n const next = new Map(values);\n const cur = next.get(wellId) ?? emptyEntry(wellId);\n next.set(wellId, { ...cur, ...patch });\n onChange(next);\n };\n\n const toggleSelect = (wellId: WellId) => {\n if (!onSelectionChange) return;\n const s = new Set(selection ?? []);\n if (s.has(wellId)) {\n s.delete(wellId);\n } else {\n s.add(wellId);\n }\n onSelectionChange(s);\n };\n\n const getFillDownPlan = (col: WellColumn<T>) => {\n if (!col.field) return null;\n\n const selectionActive = !!selection && selection.size > 0;\n const sourceRows = selectionActive ? rows.filter(({ id }) => selection.has(id)) : pagedRows;\n const sourceIndex = sourceRows.findIndex(({ row }) => hasFillValue(row[col.field!]));\n if (sourceIndex < 0) return null;\n\n const source = sourceRows[sourceIndex];\n const targets = selectionActive\n ? sourceRows.filter(({ id }) => id !== source.id)\n : sourceRows.slice(sourceIndex + 1);\n\n return {\n field: col.field,\n source,\n targets,\n value: source.row[col.field],\n };\n };\n\n const fillColumnDown = (col: WellColumn<T>) => {\n const plan = getFillDownPlan(col);\n if (!plan || plan.targets.length === 0) return;\n\n const next = new Map(values);\n plan.targets.forEach(({ id }) => {\n const cur = next.get(id) ?? emptyEntry(id);\n next.set(id, { ...cur, [plan.field]: plan.value } as T);\n });\n onChange(next);\n };\n\n const renderSelectCellEditable = (\n field: WellField<T>,\n fieldKey: keyof T & string,\n value: unknown,\n wellId: WellId,\n ariaLabel: string,\n ) => (\n <Select\n value={(value as string | undefined) ?? \"\"}\n onValueChange={(v) => updateRow(wellId, { [fieldKey]: v } as Partial<T>)}\n >\n <SelectTrigger size=\"sm\" className={cn(\"h-7 w-full\", CELL_EDITOR_CLASS)} aria-label={ariaLabel}>\n <SelectValue placeholder={field.placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n <span className=\"inline-flex items-center gap-2\">\n {opt.swatch ? (\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: opt.swatch }}\n aria-hidden\n />\n ) : null}\n {opt.label}\n </span>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n\n const renderBooleanCellEditable = (\n field: WellField<T>,\n fieldKey: keyof T & string,\n value: unknown,\n wellId: WellId,\n ariaLabel: string,\n ) => {\n const checked = !!value;\n if (field.boolStyle === \"switch\") {\n return (\n <Switch\n aria-label={ariaLabel}\n checked={checked}\n onCheckedChange={(c) => updateRow(wellId, { [fieldKey]: c } as Partial<T>)}\n />\n );\n }\n return (\n <Checkbox\n aria-label={ariaLabel}\n checked={checked}\n onCheckedChange={(c) => updateRow(wellId, { [fieldKey]: c === true } as Partial<T>)}\n />\n );\n };\n\n const renderInputCellEditable = (\n field: WellField<T>,\n fieldKey: keyof T & string,\n value: unknown,\n wellId: WellId,\n ariaLabel: string,\n ) => (\n <Input\n type={resolveInputType(field.kind)}\n step={field.kind === \"integer\" ? 1 : undefined}\n placeholder={field.placeholder}\n aria-label={ariaLabel}\n className={cn(\"h-7\", CELL_EDITOR_CLASS)}\n value={(value as string | number | undefined) ?? \"\"}\n onChange={(e) => updateRow(wellId, { [fieldKey]: parseInputValue(field.kind, e.target.value) } as Partial<T>)}\n />\n );\n\n const renderEditableCell = (field: WellField<T>, col: WellColumn<T>, wellId: WellId, row: T) => {\n const fieldKey = col.field!;\n const value = row[fieldKey];\n const ariaLabel = `${field.label} for ${wellId}`;\n\n if (field.kind === \"select\") return renderSelectCellEditable(field, fieldKey, value, wellId, ariaLabel);\n if (field.kind === \"multiselect\") {\n const current = Array.isArray(value) ? (value as string[]) : [];\n return (\n <MultiSelectCell\n ariaLabel={ariaLabel}\n value={current}\n options={field.options ?? []}\n placeholder={field.placeholder}\n onChange={(next) => updateRow(wellId, { [fieldKey]: next.length === 0 ? undefined : next } as Partial<T>)}\n />\n );\n }\n if (field.kind === \"boolean\") return renderBooleanCellEditable(field, fieldKey, value, wellId, ariaLabel);\n return renderInputCellEditable(field, fieldKey, value, wellId, ariaLabel);\n };\n\n const renderReadonlyCell = (field: WellField<T> | undefined, value: unknown) => {\n if (field?.kind === \"select\") {\n const option = (field.options ?? []).find((opt) => opt.value === value);\n if (!option) {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n\n return (\n <Badge variant=\"secondary\">\n {option.swatch ? (\n <span\n className=\"size-2 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: option.swatch }}\n aria-hidden\n />\n ) : null}\n {option.label}\n </Badge>\n );\n }\n\n if (field?.kind === \"multiselect\") {\n const arr = Array.isArray(value) ? (value as string[]) : [];\n if (arr.length === 0) {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n return (\n <div className=\"flex flex-wrap gap-1\">\n {arr.map((v) => {\n const opt = (field.options ?? []).find((o) => o.value === v);\n return (\n <Badge key={v} variant=\"secondary\">\n {opt?.swatch ? (\n <span\n className=\"size-2 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: opt.swatch }}\n aria-hidden\n />\n ) : null}\n {opt?.label ?? v}\n </Badge>\n );\n })}\n </div>\n );\n }\n\n if (field?.kind === \"boolean\") {\n return value ? (\n <Check aria-label=\"Yes\" className=\"size-4 text-foreground\" />\n ) : (\n <span className=\"text-muted-foreground\">—</span>\n );\n }\n\n if (!hasFillValue(value)) {\n return <span className=\"text-muted-foreground\">—</span>;\n }\n\n return String(value);\n };\n\n const renderCell = (col: WellColumn<T>, wellId: WellId, row: T) => {\n if (col.render) {\n return col.render({\n row,\n wellId,\n update: (patch) => updateRow(wellId, patch),\n });\n }\n if (!col.field) return null;\n\n const field = fieldByKey.get(col.field);\n const value = row[col.field];\n\n if (field?.editableInTable && field.kind !== \"custom\") {\n return renderEditableCell(field, col, wellId, row);\n }\n\n return renderReadonlyCell(field, value);\n };\n\n const renderColumnHeader = (col: WellColumn<T>) => {\n const matchingField = col.field ? fieldByKey.get(col.field) : undefined;\n const icon = col.icon ?? matchingField?.icon;\n const fillPlan = enableFillDown ? getFillDownPlan(col) : null;\n const canFillDown = !!fillPlan && fillPlan.targets.length > 0;\n const fillHint =\n selection && selection.size > 0\n ? `Fill selected ${col.header} cells from the first selected value`\n : `Fill visible ${col.header} cells downward from the first value`;\n return (\n <span className=\"flex min-w-0 items-center justify-between gap-2\">\n <span className=\"inline-flex min-w-0 items-center gap-1.5\">\n {icon ? <span className=\"inline-flex text-muted-foreground [&_svg]:size-3.5\">{icon}</span> : null}\n <span className=\"truncate\">{col.header}</span>\n </span>\n {enableFillDown && col.field ? (\n <Tooltip>\n <TooltipTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n className=\"-mr-1 text-muted-foreground hover:text-foreground\"\n disabled={!canFillDown}\n aria-label={`Fill down ${col.header}`}\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n fillColumnDown(col);\n }}\n >\n <ArrowDownToLine />\n </Button>\n </TooltipTrigger>\n <TooltipContent>{fillHint}</TooltipContent>\n </Tooltip>\n ) : null}\n </span>\n );\n };\n\n const groupableColumns = React.useMemo(() => columns.filter((col) => !!col.field), [columns]);\n const activeGroupField = groupable && groupByField ? groupByField : \"\";\n const grouped = React.useMemo(\n () => (activeGroupField ? groupRowsBy(rows, activeGroupField) : null),\n [activeGroupField, rows],\n );\n\n const totalRows = rows.length;\n const lastPage = Math.max(0, Math.ceil(totalRows / pageSize) - 1);\n const selSize = selection?.size ?? 0;\n const totalColSpan = columns.length + 1 + (onSelectionChange ? 1 : 0);\n\n const renderDataRow = (id: WellId, row: T) => {\n const isSelected = selection?.has(id);\n const extra = rowProps?.({ wellId: id, row, isSelected: !!isSelected });\n return (\n <TableRow key={id} {...extra}>\n {onSelectionChange ? (\n <TableCell className=\"w-10\">\n <Checkbox checked={!!isSelected} onCheckedChange={() => toggleSelect(id)} aria-label={`Select ${id}`} />\n </TableCell>\n ) : null}\n <TableCell className=\"font-semibold\">{id}</TableCell>\n {columns.map((col) => (\n <TableCell\n key={col.id ?? col.field ?? col.header}\n style={col.minWidth ? { minWidth: col.minWidth } : undefined}\n >\n {renderCell(col, id, row)}\n </TableCell>\n ))}\n </TableRow>\n );\n };\n\n const toggleGroup = (key: string) => {\n setCollapsedGroups((prev) => {\n const next = new Set(prev);\n if (next.has(key)) next.delete(key);\n else next.add(key);\n return next;\n });\n };\n\n const groupHeaderLabel = (() => {\n if (!activeGroupField) return \"\";\n const col = columns.find((c) => c.field === activeGroupField);\n return col?.header ?? activeGroupField;\n })();\n\n return (\n <div data-slot=\"well-manifest-table\" className={cn(\"flex flex-col gap-2\", className)}>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setShowAll((v) => !v)}>\n {showAll ? \"Hide empty wells\" : \"Show all wells\"}\n </Button>\n {filterable ? (\n <ManifestFilterPopover columns={resolvedFilterColumns} filters={filters} onFiltersChange={setFilters} />\n ) : null}\n {groupable ? (\n <div className=\"inline-flex items-center gap-1.5\">\n <Layers aria-hidden className=\"size-3.5 text-muted-foreground\" />\n <Select value={groupByField || \"__none\"} onValueChange={(v) => setGroupByField(v === \"__none\" ? \"\" : v)}>\n <SelectTrigger size=\"sm\" className=\"h-7 min-w-40\" aria-label=\"Group by\">\n <SelectValue placeholder=\"Group by…\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__none\">No grouping</SelectItem>\n {groupableColumns.map((col) => (\n <SelectItem key={col.id ?? col.field} value={col.field ?? \"\"}>\n {col.header}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n <span className=\"text-xs text-muted-foreground\">\n {totalRows} rows · {selSize} selected\n </span>\n </div>\n\n <TooltipProvider>\n <Table data-density=\"default\">\n <TableHeader>\n <TableRow>\n {onSelectionChange ? (\n <TableHead className=\"w-10 text-center\">\n <span className=\"inline-flex items-center justify-center text-muted-foreground [&_svg]:size-3.5\">\n <Check aria-hidden />\n <span className=\"sr-only\">Selected</span>\n </span>\n </TableHead>\n ) : null}\n <TableHead style={{ minWidth: 60 }}>Well</TableHead>\n {columns.map((col) => (\n <TableHead\n key={col.id ?? col.field ?? col.header}\n style={col.minWidth ? { minWidth: col.minWidth } : undefined}\n >\n {renderColumnHeader(col)}\n </TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {grouped\n ? grouped.map((group) => {\n const isCollapsed = collapsedGroups.has(group.key);\n return (\n <React.Fragment key={group.key}>\n <TableRow className=\"cursor-pointer bg-muted/40\" onClick={() => toggleGroup(group.key)}>\n <TableCell colSpan={totalColSpan} className=\"py-1.5\">\n <div className=\"flex items-center gap-2 text-xs font-medium\">\n {isCollapsed ? (\n <ChevronRight aria-hidden className=\"size-3.5 text-muted-foreground\" />\n ) : (\n <ChevronDown aria-hidden className=\"size-3.5 text-muted-foreground\" />\n )}\n <span>\n {groupHeaderLabel}: {group.key}\n </span>\n <span className=\"text-muted-foreground\">\n ({group.rows.length} {group.rows.length === 1 ? \"row\" : \"rows\"})\n </span>\n </div>\n </TableCell>\n </TableRow>\n {isCollapsed ? null : group.rows.map(({ id, row }) => renderDataRow(id, row))}\n </React.Fragment>\n );\n })\n : pagedRows.map(({ id, row }) => renderDataRow(id, row))}\n {(grouped ? grouped.length === 0 : pagedRows.length === 0) ? (\n <TableRow>\n <TableCell colSpan={totalColSpan} className=\"text-xs text-muted-foreground\">\n No rows. Paint wells on the plate.\n </TableCell>\n </TableRow>\n ) : null}\n </TableBody>\n </Table>\n </TooltipProvider>\n\n {grouped ? null : (\n <div className=\"flex items-center justify-end gap-3 text-xs text-muted-foreground\">\n <span className=\"text-muted-foreground\">Rows per page</span>\n <Select\n value={String(pageSize)}\n onValueChange={(v) => {\n setPageSize(parseInt(v, 10));\n setPage(0);\n }}\n >\n <SelectTrigger size=\"sm\" className=\"h-7 w-18\" aria-label=\"Rows per page\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {pageSizeOptions.map((s) => (\n <SelectItem key={s} value={String(s)}>\n {s}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <span>\n {totalRows === 0\n ? \"0 of 0\"\n : `${page * pageSize + 1}–${Math.min((page + 1) * pageSize, totalRows)} of ${totalRows}`}\n </span>\n <Button variant=\"outline\" size=\"sm\" disabled={page === 0} onClick={() => setPage((p) => Math.max(0, p - 1))}>\n Prev\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={page >= lastPage}\n onClick={() => setPage((p) => Math.min(lastPage, p + 1))}\n >\n Next\n </Button>\n </div>\n )}\n </div>\n );\n}\n"],"names":["MultiSelectCell","ariaLabel","value","options","placeholder","onChange","anchorRef","useComboboxAnchor","labelByValue","React","m","o","jsxs","Combobox","ComboboxChips","cn","CELL_EDITOR_CLASS","jsx","ComboboxValue","items","item","ComboboxChip","ComboboxChipsInput","ComboboxContent","ComboboxEmpty","ComboboxList","ComboboxItem","PAGE_SIZE_SMALL","PAGE_SIZE_MEDIUM","PAGE_SIZE_LARGE","DEFAULT_PAGE_SIZE","DEFAULT_PAGE_SIZE_OPTIONS","hasFillValue","INPUT_TYPE_BY_KIND","resolveInputType","kind","parseInputValue","raw","num","groupRowsBy","rows","field","map","entry","key","existing","inferFilterColumns","columns","col","WellManifestTable","values","fields","selection","onSelectionChange","emptyEntry","isPopulated","initialPageSize","pageSizeOptions","enableFillDown","rowProps","filterable","filterColumns","groupable","className","showAll","setShowAll","page","setPage","pageSize","setPageSize","filters","setFilters","groupByField","setGroupByField","collapsedGroups","setCollapsedGroups","fieldByKey","f","resolvedFilterColumns","arr","id","row","filtered","r","record","condition","cellRaw","cellValue","applyFilterCondition","have","p","a","b","pagedRows","updateRow","wellId","patch","next","cur","toggleSelect","s","getFillDownPlan","selectionActive","sourceRows","sourceIndex","source","targets","fillColumnDown","plan","renderSelectCellEditable","fieldKey","Select","v","SelectTrigger","SelectValue","SelectContent","opt","SelectItem","renderBooleanCellEditable","checked","Switch","c","Checkbox","renderInputCellEditable","Input","e","renderEditableCell","current","renderReadonlyCell","option","Badge","Check","renderCell","renderColumnHeader","matchingField","icon","fillPlan","canFillDown","fillHint","Tooltip","TooltipTrigger","Button","ArrowDownToLine","TooltipContent","groupableColumns","activeGroupField","grouped","totalRows","lastPage","selSize","totalColSpan","renderDataRow","isSelected","extra","TableRow","TableCell","toggleGroup","prev","groupHeaderLabel","ManifestFilterPopover","Layers","TooltipProvider","Table","TableHeader","TableHead","TableBody","group","isCollapsed","ChevronRight","ChevronDown"],"mappings":"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"}