@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.156.1 → 1.0.0-beta.158.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 (73) hide show
  1. package/README.md +113 -0
  2. package/dist/components/composed/DataAppShell/RightPanel.cjs +1 -1
  3. package/dist/components/composed/DataAppShell/RightPanel.cjs.map +1 -1
  4. package/dist/components/composed/DataAppShell/RightPanel.d.ts +11 -1
  5. package/dist/components/composed/DataAppShell/RightPanel.js +128 -126
  6. package/dist/components/composed/DataAppShell/RightPanel.js.map +1 -1
  7. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +1 -1
  8. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -1
  9. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.d.ts +5 -1
  10. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +32 -30
  11. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -1
  12. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +1 -1
  13. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -1
  14. package/dist/components/composed/PlateMapEditor/PlateMapEditor.d.ts +269 -13
  15. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +392 -310
  16. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -1
  17. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs +1 -1
  18. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs.map +1 -1
  19. package/dist/components/composed/PlateMapEditor/PlateMapForm.d.ts +5 -1
  20. package/dist/components/composed/PlateMapEditor/PlateMapForm.js +19 -15
  21. package/dist/components/composed/PlateMapEditor/PlateMapForm.js.map +1 -1
  22. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs +1 -1
  23. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs.map +1 -1
  24. package/dist/components/composed/PlateMapEditor/PlateMapGrid.d.ts +12 -1
  25. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js +69 -63
  26. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js.map +1 -1
  27. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs +1 -1
  28. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs.map +1 -1
  29. package/dist/components/composed/PlateMapEditor/PlateMapManifest.d.ts +13 -4
  30. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js +38 -29
  31. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js.map +1 -1
  32. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +1 -1
  33. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -1
  34. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.d.ts +6 -1
  35. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +490 -275
  36. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -1
  37. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +1 -1
  38. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -1
  39. package/dist/components/composed/PlateMapEditor/WellLegend.d.ts +8 -2
  40. package/dist/components/composed/PlateMapEditor/WellLegend.js +8 -7
  41. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -1
  42. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +1 -1
  43. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -1
  44. package/dist/components/composed/PlateMapEditor/WellManifestTable.d.ts +6 -2
  45. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +288 -256
  46. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -1
  47. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +1 -1
  48. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -1
  49. package/dist/components/composed/PlateMapEditor/WellMetadataForm.d.ts +5 -1
  50. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +61 -66
  51. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -1
  52. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs +2 -0
  53. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs.map +1 -0
  54. package/dist/components/composed/PlateMapEditor/WellQuickPaint.d.ts +23 -0
  55. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js +75 -0
  56. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js.map +1 -0
  57. package/dist/components/composed/PlateMapEditor/index.d.ts +6 -2
  58. package/dist/components/composed/PlateMapEditor/types.d.ts +65 -0
  59. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs +2 -0
  60. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs.map +1 -0
  61. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.d.ts +109 -0
  62. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js +246 -0
  63. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js.map +1 -0
  64. package/dist/composed/PlateMapEditor.cjs +1 -1
  65. package/dist/composed/PlateMapEditor.js +48 -42
  66. package/dist/composed/PlateMapEditor.js.map +1 -1
  67. package/dist/index.cjs +1 -1
  68. package/dist/index.css +1 -1
  69. package/dist/index.js +701 -695
  70. package/dist/index.js.map +1 -1
  71. package/dist/index.scoped.css +1 -1
  72. package/dist/index.tailwind.css +1 -1
  73. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PlateMapEditor.js","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapEditor.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { plateOptionsFromCsvTriage } from \"./csvPlateTriage\";\nimport { PlateMapActionsMenu } from \"./PlateMapActionsMenu\";\nimport { PlateMapForm } from \"./PlateMapForm\";\nimport { PlateMapGrid } from \"./PlateMapGrid\";\nimport { PlateMapManifest } from \"./PlateMapManifest\";\nimport { PlateMapPlateSelector } from \"./PlateMapPlateSelector\";\n\nimport type { PlateMapActionsMenuProps } from \"./PlateMapActionsMenu\";\nimport type { PlateMapPlateSelectorVariant } from \"./PlateMapPlateSelector\";\nimport type { WellShape } from \"./PlatePaintGrid\";\nimport type {\n PlateFormat,\n PlateMapCsvTriage,\n PlateMapGroupOption,\n PlateMapPlateOption,\n WellColumn,\n WellField,\n WellId,\n WellRecord,\n} from \"./types\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardAction, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\nconst PLATE_WELL_KEY_SEPARATOR = \"::\";\nconst DEFAULT_PLATE_BARCODE_FIELD = \"plateBarcode\";\nconst DEFAULT_PLATE_BARCODE_HEADER = \"Plate Barcode\";\n\nexport function getPlateMapScopedWellId(plateBarcode: string, wellId: WellId): WellId {\n return `${plateBarcode}${PLATE_WELL_KEY_SEPARATOR}${wellId}`;\n}\n\nfunction wellIdFromPlateWellKey(plateId: string, key: WellId): WellId | undefined {\n const prefix = `${plateId}${PLATE_WELL_KEY_SEPARATOR}`;\n return key.startsWith(prefix) ? key.slice(prefix.length) : undefined;\n}\n\nexport interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends Omit<\n PlateMapActionsMenuProps,\n \"hasEntries\" | \"className\"\n> {\n format: PlateFormat;\n rows?: number;\n columns?: number;\n values: Map<WellId, T>;\n onChange: (next: Map<WellId, T>) => void;\n selection: Set<WellId>;\n onSelectionChange: (next: Set<WellId>) => void;\n\n fields: WellField<T>[];\n tableColumns: WellColumn<T>[];\n\n /** Resolves the SVG fill color for a well. */\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n /** Builds an empty record when a well is freshly created. */\n emptyEntry: (wellId: WellId) => T;\n /**\n * Merges the staged form record onto an existing well record on Apply.\n * Defaults to a shallow merge (form keys overwrite existing keys when set).\n */\n mergeOnApply?: (existing: T | undefined, staged: Partial<T>, wellId: WellId) => T;\n /** Filter for the manifest's \"hide empty\" mode. */\n isPopulated?: (row: T) => boolean;\n /** Select field cycled when double-clicking a single well, e.g. role painting. */\n cycleFieldOnWellDoubleClick?: keyof T & string;\n\n /** Optional header title (e.g. plate set name). */\n title?: string;\n /** Optional badges shown in the header next to the title. */\n badges?: React.ReactNode;\n /** Optional banner (e.g. import/error alert) shown above the layout. */\n banner?: React.ReactNode;\n /** Legend block rendered under the form column. */\n legend?: React.ReactNode;\n /** Form helper slot rendered between fields and the action row. */\n formExtras?: React.ReactNode;\n /**\n * Fully replaces the left column. When set, the built-in `WellMetadataForm`\n * is omitted — use this for alternative workflows like a drag-and-drop\n * source palette. The legend slot still renders beneath the replacement.\n */\n formSlot?: React.ReactNode;\n /** Footer actions (e.g. Save, Back). */\n footer?: React.ReactNode;\n /** Title for the plate grid panel. */\n plateTitle?: React.ReactNode;\n /** Optional controls shown next to the built-in actions menu. */\n plateToolbar?: React.ReactNode;\n /** User-provided plates available for this editor. Barcodes are never generated by the editor. */\n plates?: PlateMapPlateOption[];\n activePlateId?: string;\n onPlateChange?: (plateId: string) => void;\n /** Opens the host app's manual barcode entry flow for creating a plate. */\n onAddPlate?: () => void;\n /** Removes a plate (typically when using `plateSelectorVariant=\"tabs\"`). */\n onRemovePlate?: (plateId: string) => void;\n addPlateLabel?: string;\n removePlateLabel?: string;\n plateSelectorLabel?: string;\n /** Layout of the plate selector. Defaults to `\"dropdown\"`. */\n plateSelectorVariant?: PlateMapPlateSelectorVariant;\n /** Row field used to stamp the active user-provided barcode onto edited wells. Defaults to `plateBarcode`. */\n plateBarcodeField?: keyof T & string;\n /** Header for the automatic manifest barcode column. */\n plateBarcodeColumnHeader?: string;\n /** Hide the automatic manifest barcode column when plate-scoped editing is active. */\n hidePlateBarcodeColumn?: boolean;\n /** Optional grouped well shortcuts rendered under the grid. */\n groups?: PlateMapGroupOption[];\n activeGroupId?: string;\n onGroupClick?: (group: PlateMapGroupOption) => void;\n /** Custom hover summary for the strip above the grid. */\n renderHoverSummary?: (well: T | undefined, wellId: WellId) => React.ReactNode;\n /** Fixed well size. When unset, the grid grows with the available width. */\n cellSize?: number;\n /** Fill color for empty wells. Pass `null` to delegate empty wells to `colorForWell`. */\n emptyWellFillColor?: string | null;\n /** Well shape forwarded to `PlatePaintGrid`. Defaults to `\"rect\"`. */\n wellShape?: WellShape;\n /** When true, wraps the grid in a card-like plate frame (rounded + border + soft shadow). */\n framedPlate?: boolean;\n /**\n * Forwarded to `PlatePaintGrid`. Render-prop that places a node inside each\n * absolute-positioned well cell — used to wire drop targets without binding\n * the kit to a specific DnD library.\n */\n wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;\n /** Wells to highlight (e.g. when hovering a legend item externally). */\n highlightedWellIds?: ReadonlySet<WellId>;\n /** Fires whenever the currently hovered well changes (null on leave). */\n onHoveredWellChange?: (wellId: WellId | null) => void;\n /** Enables the filter popover on the manifest table. */\n manifestFilterable?: boolean;\n /** Enables the group-by selector on the manifest table. */\n manifestGroupable?: boolean;\n autoScaleGrid?: boolean;\n minCellSize?: number;\n maxCellSize?: number;\n\n className?: string;\n}\n\nfunction defaultMerge<T extends WellRecord>(existing: T | undefined, staged: Partial<T>): T {\n return { ...(existing ?? ({} as T)), ...staged };\n}\n\nfunction mergePlateOptions(\n importedPlates: PlateMapPlateOption[],\n providedPlates: PlateMapPlateOption[] | undefined,\n): PlateMapPlateOption[] {\n const merged = new Map<string, PlateMapPlateOption>();\n importedPlates.forEach((plate) => merged.set(plate.id, plate));\n (providedPlates ?? []).forEach((plate) => merged.set(plate.id, plate));\n return [...merged.values()];\n}\n\nfunction resolveActivePlate(\n plates: PlateMapPlateOption[],\n selectedPlateId: string | undefined,\n): PlateMapPlateOption | undefined {\n const matchingPlate = plates.find((plate) => plate.id === selectedPlateId);\n if (matchingPlate) return matchingPlate;\n return selectedPlateId ? undefined : plates[0];\n}\n\nfunction buildScopedValues<T extends WellRecord>(values: Map<WellId, T>, plateKey: string | undefined) {\n if (!plateKey) return values;\n\n const next = new Map<WellId, T>();\n values.forEach((row, key) => {\n const wellId = wellIdFromPlateWellKey(plateKey, key);\n if (wellId) next.set(wellId, row);\n });\n return next;\n}\n\nfunction resolveImportedPlateSelection({\n importedPlates,\n selectedPlateId,\n isControlled,\n providedPlateCount,\n}: {\n importedPlates: PlateMapPlateOption[];\n selectedPlateId?: string;\n isControlled: boolean;\n providedPlateCount: number;\n}): string | null | undefined {\n const firstImportedPlateId = importedPlates[0]?.id;\n if (!firstImportedPlateId) return !isControlled && providedPlateCount === 0 ? null : undefined;\n return importedPlates.some((plate) => plate.id === selectedPlateId) ? undefined : firstImportedPlateId;\n}\n\nfunction shouldShowPlateSelector(plateCount: number, onAddPlate: PlateMapEditorProps[\"onAddPlate\"]): boolean {\n return plateCount > 0 || !!onAddPlate;\n}\n\nfunction canUpdatePlateSelection(isControlled: boolean, onPlateChange: PlateMapEditorProps[\"onPlateChange\"]): boolean {\n return !isControlled || !!onPlateChange;\n}\n\nfunction PlateMapEditorTitleBar({ title, badges }: { title?: string; badges?: React.ReactNode }) {\n if (!title && !badges) return null;\n\n return (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n {title ? <h2 className=\"text-lg font-semibold\">{title}</h2> : <span />}\n {badges ? <div className=\"flex flex-wrap gap-2\">{badges}</div> : null}\n </div>\n );\n}\n\nexport function PlateMapEditor<T extends WellRecord = WellRecord>({\n format,\n rows,\n columns,\n values,\n onChange,\n selection,\n onSelectionChange,\n fields,\n tableColumns,\n colorForWell,\n emptyEntry,\n mergeOnApply,\n isPopulated,\n cycleFieldOnWellDoubleClick,\n title,\n badges,\n banner,\n legend,\n formExtras,\n formSlot,\n footer,\n plateTitle = \"Plate\",\n plateToolbar,\n plates,\n activePlateId,\n onPlateChange,\n onAddPlate,\n onRemovePlate,\n addPlateLabel,\n removePlateLabel,\n plateSelectorLabel,\n plateSelectorVariant,\n plateBarcodeField,\n plateBarcodeColumnHeader = DEFAULT_PLATE_BARCODE_HEADER,\n hidePlateBarcodeColumn = false,\n groups,\n activeGroupId,\n onGroupClick,\n renderHoverSummary,\n cellSize,\n emptyWellFillColor,\n wellShape,\n framedPlate,\n wrapWell,\n highlightedWellIds,\n onHoveredWellChange,\n manifestFilterable,\n manifestGroupable,\n autoScaleGrid,\n minCellSize,\n maxCellSize,\n className,\n templates,\n templateId,\n onTemplateChange,\n onClearTemplate,\n onImportCsv,\n onExportCsv,\n onImportTemplate,\n onExportTemplate,\n csvAccept,\n templateAccept,\n label,\n align,\n side,\n importTemplateLabel,\n exportTemplateLabel,\n importCsvLabel,\n exportCsvLabel,\n clearLabel,\n}: PlateMapEditorProps<T>) {\n const [staged, setStaged] = React.useState<Partial<T>>({});\n const [hoverPos, setHoverPos] = React.useState<WellId | null>(null);\n const [flashWell, setFlashWell] = React.useState<{ wellId: WellId; key: number }>();\n const [csvPlates, setCsvPlates] = React.useState<PlateMapPlateOption[]>([]);\n const [internalActivePlateId, setInternalActivePlateId] = React.useState<string>();\n\n const merge = mergeOnApply ?? defaultMerge<T>;\n const availablePlates = React.useMemo(() => mergePlateOptions(csvPlates, plates), [csvPlates, plates]);\n const isPlateSelectionControlled = activePlateId !== undefined;\n const selectedPlateId = activePlateId ?? internalActivePlateId;\n const activePlate = React.useMemo(\n () => resolveActivePlate(availablePlates, selectedPlateId),\n [availablePlates, selectedPlateId],\n );\n const activePlateBarcode = activePlate?.barcode;\n const activePlateKey = activePlateBarcode;\n const isPlateScoped = !!activePlateKey;\n const barcodeField = (plateBarcodeField ?? DEFAULT_PLATE_BARCODE_FIELD) as keyof T & string;\n const previousActivePlateKey = React.useRef<string | undefined>(activePlateKey);\n\n const scopedValues = React.useMemo(() => buildScopedValues(values, activePlateKey), [activePlateKey, values]);\n\n const toStoredWellKey = React.useCallback(\n (wellId: WellId): WellId => {\n if (!isPlateScoped || !activePlateKey) return wellId;\n return getPlateMapScopedWellId(activePlateKey, wellId);\n },\n [activePlateKey, isPlateScoped],\n );\n\n const handlePlateChange = React.useCallback(\n (plateId: string) => {\n if (!isPlateSelectionControlled) setInternalActivePlateId(plateId);\n onPlateChange?.(plateId);\n },\n [isPlateSelectionControlled, onPlateChange],\n );\n\n const handleImportCsv = React.useCallback(\n async (file: File, triage?: PlateMapCsvTriage) => {\n if (triage) {\n const nextCsvPlates = plateOptionsFromCsvTriage(triage);\n setCsvPlates(nextCsvPlates);\n const nextSelection = resolveImportedPlateSelection({\n importedPlates: nextCsvPlates,\n selectedPlateId,\n isControlled: isPlateSelectionControlled,\n providedPlateCount: plates?.length ?? 0,\n });\n\n if (nextSelection !== undefined) {\n if (!isPlateSelectionControlled) setInternalActivePlateId(nextSelection ?? undefined);\n if (nextSelection) onPlateChange?.(nextSelection);\n }\n }\n\n await onImportCsv?.(file, triage);\n },\n [isPlateSelectionControlled, onImportCsv, onPlateChange, plates, selectedPlateId],\n );\n\n const stampActivePlateBarcode = React.useCallback(\n (row: T): T => {\n if (!isPlateScoped || !activePlateBarcode) return row;\n return { ...(row as Record<string, unknown>), [barcodeField]: activePlateBarcode } as T;\n },\n [activePlateBarcode, barcodeField, isPlateScoped],\n );\n\n const commitScopedValues = React.useCallback(\n (nextScopedValues: Map<WellId, T>) => {\n if (!isPlateScoped || !activePlateKey) {\n onChange(nextScopedValues);\n return;\n }\n\n const next = new Map(values);\n [...next.keys()].forEach((key) => {\n if (wellIdFromPlateWellKey(activePlateKey, key)) {\n next.delete(key);\n }\n });\n nextScopedValues.forEach((row, wellId) => {\n next.set(toStoredWellKey(wellId), stampActivePlateBarcode(row));\n });\n onChange(next);\n },\n [activePlateKey, isPlateScoped, onChange, stampActivePlateBarcode, toStoredWellKey, values],\n );\n\n React.useEffect(() => {\n if (previousActivePlateKey.current === activePlateKey) return;\n previousActivePlateKey.current = activePlateKey;\n setStaged({});\n setHoverPos(null);\n setFlashWell(undefined);\n if (selection.size > 0) onSelectionChange(new Set());\n }, [activePlateKey, onSelectionChange, selection.size]);\n\n const doubleClickCycleField = React.useMemo(() => {\n if (!cycleFieldOnWellDoubleClick) return;\n const field = fields.find((f) => f.key === cycleFieldOnWellDoubleClick);\n if (field?.kind !== \"select\" || !field.options?.length) return;\n return field;\n }, [cycleFieldOnWellDoubleClick, fields]);\n\n const applyStagedToSelection = () => {\n if (selection.size === 0) return;\n const next = new Map(scopedValues);\n selection.forEach((wellId) => {\n const existing = next.get(wellId);\n const base = existing ?? emptyEntry(wellId);\n const merged = stampActivePlateBarcode(merge(base, staged, wellId));\n next.set(wellId, merged);\n });\n commitScopedValues(next);\n };\n\n const clearWells = () => {\n if (selection.size === 0) return;\n const next = new Map(scopedValues);\n selection.forEach((wellId) => next.delete(wellId));\n commitScopedValues(next);\n };\n\n const cycleWellField = React.useCallback(\n (wellId: WellId) => {\n if (!doubleClickCycleField?.options?.length) return;\n\n const next = new Map(scopedValues);\n const existing = next.get(wellId);\n const currentValue = existing?.[doubleClickCycleField.key];\n const currentIndex = doubleClickCycleField.options.findIndex((opt) => opt.value === currentValue);\n const nextOption = doubleClickCycleField.options[(currentIndex + 1) % doubleClickCycleField.options.length];\n if (!nextOption) return;\n\n const base = existing ?? emptyEntry(wellId);\n next.set(wellId, stampActivePlateBarcode({ ...base, [doubleClickCycleField.key]: nextOption.value } as T));\n commitScopedValues(next);\n setFlashWell((current) => ({ wellId, key: (current?.key ?? 0) + 1 }));\n },\n [commitScopedValues, doubleClickCycleField, emptyEntry, scopedValues, stampActivePlateBarcode],\n );\n\n const manifestColumns = React.useMemo(() => {\n if (!isPlateScoped || !activePlateBarcode || hidePlateBarcodeColumn) return tableColumns;\n const alreadyHasBarcodeColumn = tableColumns.some(\n (column) => column.field === barcodeField || column.id === barcodeField,\n );\n if (alreadyHasBarcodeColumn) return tableColumns;\n\n const barcodeColumn: WellColumn<T> = {\n id: barcodeField,\n header: plateBarcodeColumnHeader,\n minWidth: 150,\n render: ({ row }) => {\n const barcode = row[barcodeField] ?? activePlateBarcode;\n return <Badge variant=\"outline\">{String(barcode)}</Badge>;\n },\n };\n return [barcodeColumn, ...tableColumns];\n }, [activePlateBarcode, barcodeField, hidePlateBarcodeColumn, isPlateScoped, plateBarcodeColumnHeader, tableColumns]);\n const showPlateSelector = shouldShowPlateSelector(availablePlates.length, onAddPlate);\n const canChangePlate = canUpdatePlateSelection(isPlateSelectionControlled, onPlateChange);\n\n return (\n <div data-slot=\"plate-map-editor\" className={cn(\"flex flex-col gap-4\", className)}>\n <PlateMapEditorTitleBar title={title} badges={badges} />\n\n {banner}\n\n <div className=\"flex flex-wrap gap-3 md:flex-nowrap\">\n {/* Form column */}\n <Card className=\"flex w-full max-w-[360px] min-w-[300px] basis-[360px] flex-col\" size=\"sm\">\n <CardContent className=\"flex h-full flex-1 flex-col gap-3\">\n <PlateMapForm\n fields={fields}\n value={staged}\n onChange={setStaged}\n selectionSize={selection.size}\n onApply={applyStagedToSelection}\n onClear={clearWells}\n extras={formExtras}\n legend={legend}\n formSlot={formSlot}\n />\n </CardContent>\n </Card>\n\n {/* Plate column */}\n <Card className=\"flex min-w-[360px] flex-1 flex-col\" size=\"sm\">\n <CardHeader className=\"border-b\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <CardTitle className=\"min-w-0\">{plateTitle}</CardTitle>\n {showPlateSelector ? (\n <PlateMapPlateSelector\n plates={availablePlates}\n activePlateId={activePlate?.id}\n onPlateChange={canChangePlate ? handlePlateChange : undefined}\n onAddPlate={onAddPlate}\n onRemovePlate={onRemovePlate}\n addPlateLabel={addPlateLabel}\n removePlateLabel={removePlateLabel}\n label={plateSelectorLabel}\n variant={plateSelectorVariant}\n />\n ) : null}\n </div>\n <CardAction className=\"flex flex-wrap items-center gap-2\">\n <PlateMapActionsMenu\n templates={templates}\n templateId={templateId}\n onTemplateChange={onTemplateChange}\n onClearTemplate={onClearTemplate}\n hasEntries={values.size > 0}\n onImportCsv={onImportCsv ? handleImportCsv : undefined}\n onExportCsv={onExportCsv}\n onImportTemplate={onImportTemplate}\n onExportTemplate={onExportTemplate}\n csvAccept={csvAccept}\n templateAccept={templateAccept}\n label={label}\n align={align}\n side={side}\n importTemplateLabel={importTemplateLabel}\n exportTemplateLabel={exportTemplateLabel}\n importCsvLabel={importCsvLabel}\n exportCsvLabel={exportCsvLabel}\n clearLabel={clearLabel}\n />\n </CardAction>\n </CardHeader>\n <CardContent>\n <PlateMapGrid\n format={format}\n rows={rows}\n columns={columns}\n values={scopedValues}\n selection={selection}\n onSelectionChange={onSelectionChange}\n colorForWell={colorForWell}\n fields={fields}\n renderHoverSummary={renderHoverSummary}\n hoveredWellId={hoverPos}\n onHoveredWellChange={(wellId) => {\n setHoverPos(wellId);\n onHoveredWellChange?.(wellId);\n }}\n toolbar={plateToolbar}\n emptyWellFillColor={emptyWellFillColor}\n wellShape={wellShape}\n framed={framedPlate}\n wrapWell={wrapWell}\n highlightedWellIds={highlightedWellIds}\n onWellDoubleClick={doubleClickCycleField ? cycleWellField : undefined}\n selectionFillMode={doubleClickCycleField ? \"well\" : \"selection\"}\n flashWellId={flashWell?.wellId}\n flashWellKey={flashWell?.key}\n cellSize={cellSize}\n autoScale={autoScaleGrid}\n minCellSize={minCellSize}\n maxCellSize={maxCellSize}\n groups={groups}\n activeGroupId={activeGroupId}\n onGroupClick={onGroupClick}\n />\n </CardContent>\n </Card>\n </div>\n\n <Card size=\"sm\">\n <CardHeader className=\"border-b\">\n <CardTitle>Sample manifest</CardTitle>\n </CardHeader>\n <CardContent>\n <PlateMapManifest\n values={scopedValues}\n onChange={commitScopedValues}\n columns={manifestColumns}\n fields={fields}\n selection={selection}\n onSelectionChange={onSelectionChange}\n emptyEntry={emptyEntry}\n isPopulated={isPopulated}\n filterable={manifestFilterable}\n groupable={manifestGroupable}\n />\n </CardContent>\n </Card>\n\n {footer ? <div className=\"flex justify-end gap-2 pt-2\">{footer}</div> : null}\n </div>\n );\n}\n\nexport { Badge as PlateBadge };\n"],"names":["PLATE_WELL_KEY_SEPARATOR","DEFAULT_PLATE_BARCODE_FIELD","DEFAULT_PLATE_BARCODE_HEADER","getPlateMapScopedWellId","plateBarcode","wellId","wellIdFromPlateWellKey","plateId","key","prefix","defaultMerge","existing","staged","mergePlateOptions","importedPlates","providedPlates","merged","plate","resolveActivePlate","plates","selectedPlateId","matchingPlate","buildScopedValues","values","plateKey","next","row","resolveImportedPlateSelection","isControlled","providedPlateCount","firstImportedPlateId","shouldShowPlateSelector","plateCount","onAddPlate","canUpdatePlateSelection","onPlateChange","PlateMapEditorTitleBar","title","badges","jsxs","jsx","PlateMapEditor","format","rows","columns","onChange","selection","onSelectionChange","fields","tableColumns","colorForWell","emptyEntry","mergeOnApply","isPopulated","cycleFieldOnWellDoubleClick","banner","legend","formExtras","formSlot","footer","plateTitle","plateToolbar","activePlateId","onRemovePlate","addPlateLabel","removePlateLabel","plateSelectorLabel","plateSelectorVariant","plateBarcodeField","plateBarcodeColumnHeader","hidePlateBarcodeColumn","groups","activeGroupId","onGroupClick","renderHoverSummary","cellSize","emptyWellFillColor","wellShape","framedPlate","wrapWell","highlightedWellIds","onHoveredWellChange","manifestFilterable","manifestGroupable","autoScaleGrid","minCellSize","maxCellSize","className","templates","templateId","onTemplateChange","onClearTemplate","onImportCsv","onExportCsv","onImportTemplate","onExportTemplate","csvAccept","templateAccept","label","align","side","importTemplateLabel","exportTemplateLabel","importCsvLabel","exportCsvLabel","clearLabel","setStaged","React","hoverPos","setHoverPos","flashWell","setFlashWell","csvPlates","setCsvPlates","internalActivePlateId","setInternalActivePlateId","merge","availablePlates","isPlateSelectionControlled","activePlate","activePlateBarcode","activePlateKey","isPlateScoped","barcodeField","previousActivePlateKey","scopedValues","toStoredWellKey","handlePlateChange","handleImportCsv","file","triage","nextCsvPlates","plateOptionsFromCsvTriage","nextSelection","stampActivePlateBarcode","commitScopedValues","nextScopedValues","doubleClickCycleField","field","f","applyStagedToSelection","base","clearWells","cycleWellField","currentValue","currentIndex","opt","nextOption","current","manifestColumns","column","barcode","Badge","showPlateSelector","canChangePlate","cn","Card","CardContent","PlateMapForm","CardHeader","CardTitle","PlateMapPlateSelector","CardAction","PlateMapActionsMenu","PlateMapGrid","PlateMapManifest"],"mappings":"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"}
1
+ {"version":3,"file":"PlateMapEditor.js","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapEditor.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { PlateMapActionsMenu } from \"./PlateMapActionsMenu\";\nimport { PlateMapForm } from \"./PlateMapForm\";\nimport { PlateMapGrid } from \"./PlateMapGrid\";\nimport { PlateMapManifest } from \"./PlateMapManifest\";\nimport { PlateMapPlateSelector } from \"./PlateMapPlateSelector\";\nimport { defaultColorForWell, getPlateMapScopedWellId, usePlateMapEditorState } from \"./usePlateMapEditorState\";\nimport { WellQuickPaint } from \"./WellQuickPaint\";\n\n\nimport type { PlateMapActionsMenuProps } from \"./PlateMapActionsMenu\";\nimport type { PlateMapPlateSelectorVariant } from \"./PlateMapPlateSelector\";\nimport type { WellShape } from \"./PlatePaintGrid\";\nimport type {\n PlateFormat,\n PlateMapEditorLabels,\n PlateMapGroupOption,\n PlateMapPlateOption,\n WellColumn,\n WellField,\n WellId,\n WellRecord,\n} from \"./types\";\nimport type { PlateMapApplyScope } from \"./usePlateMapEditorState\";\nimport type { FilterColumnConfig } from \"@/components/ui/data-table/data-table\";\n\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardAction, CardContent, CardFooter, CardHeader, CardTitle } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_PLATE_BARCODE_FIELD = \"plateBarcode\";\nconst DEFAULT_PLATE_BARCODE_HEADER = \"Plate Barcode\";\nconst DEFAULT_MANIFEST_TITLE = \"Sample manifest\";\nconst DEFAULT_FORM_WIDTH = \"360px\";\n\n/**\n * Width at which the form and plate stop stacking and sit side by side.\n *\n * These are **container** widths, not viewport widths — the editor measures the\n * space it is actually given, so it lays out correctly inside a narrow panel,\n * split pane, or drawer on a wide monitor, not just on a small device. Below\n * the chosen width both regions are full-width and stacked.\n *\n * `\"sm\"` 640px · `\"md\"` 768px (default) · `\"lg\"` 1024px · `\"xl\"` 1280px ·\n * `\"never\"` always stacked.\n */\nexport type PlateMapEditorStackAt = \"sm\" | \"md\" | \"lg\" | \"xl\" | \"never\";\n\n/** Where the metadata form sits relative to the plate grid. */\nexport type PlateMapEditorFormPlacement = \"start\" | \"end\" | \"top\" | \"bottom\";\n\n/**\n * Static container-query class maps. Tailwind scans source text, so these\n * cannot be built by interpolation — every class that ships must appear here\n * verbatim.\n */\nconst SIDE_BY_SIDE_AT: Record<PlateMapEditorStackAt, string> = {\n sm: \"@min-[640px]:flex-row @min-[640px]:items-start\",\n md: \"@min-[768px]:flex-row @min-[768px]:items-start\",\n lg: \"@min-[1024px]:flex-row @min-[1024px]:items-start\",\n xl: \"@min-[1280px]:flex-row @min-[1280px]:items-start\",\n never: \"\",\n};\n\nconst FORM_WIDTH_AT: Record<PlateMapEditorStackAt, string> = {\n sm: \"@min-[640px]:w-[var(--plate-map-form-width)] @min-[640px]:shrink-0\",\n md: \"@min-[768px]:w-[var(--plate-map-form-width)] @min-[768px]:shrink-0\",\n lg: \"@min-[1024px]:w-[var(--plate-map-form-width)] @min-[1024px]:shrink-0\",\n xl: \"@min-[1280px]:w-[var(--plate-map-form-width)] @min-[1280px]:shrink-0\",\n never: \"\",\n};\n\nexport { getPlateMapScopedWellId };\n\n/**\n * Imperative handle for driving the editor's staged-edit state from outside —\n * the supported way to render the metadata form somewhere the editor can't\n * reach (a host sidebar, a drawer, a toolbar) while keeping the editor's\n * selection and apply behaviour.\n *\n * Pair with `hideForm` and your own form:\n *\n * ```tsx\n * const editor = React.useRef<PlateMapEditorHandle<MyWell>>(null);\n * <PlateMapEditor ref={editor} hideForm … />\n * <MySidebarForm\n * onChange={(next) => editor.current?.setStaged(next)}\n * onApply={() => editor.current?.apply()}\n * onClear={() => editor.current?.clear()}\n * />\n * ```\n */\n/**\n * Per-region class hooks. Each card also carries\n * `data-plate-map-region=\"form|plate|manifest\"`, so plain CSS can target the\n * same regions without threading props.\n */\nexport interface PlateMapEditorClassNames {\n /** The row (or column) holding the form and plate cards. */\n layout?: string;\n /** The form column `Card`. Merged after the width rules, so width utilities here win. */\n formCard?: string;\n /** The plate column `Card`. */\n plateCard?: string;\n /** The manifest `Card`. */\n manifestCard?: string;\n /** The `PlateMapForm` panel inside the form card. */\n form?: string;\n /** The `PlateMapGrid` panel inside the plate card. */\n grid?: string;\n /** The `PlateMapManifest` panel inside the manifest card. */\n manifest?: string;\n}\n\n/** Manifest table behaviour. Structural bits (`hideManifest`, `manifestTitle`, `manifestSlot`) stay top-level. */\nexport interface PlateMapEditorManifestOptions {\n /** Enables the filter popover. */\n filterable?: boolean;\n /** Overrides which columns are filterable and how. */\n filterColumns?: FilterColumnConfig[];\n /** Enables the group-by selector. */\n groupable?: boolean;\n /** Field grouped by on first render. Requires `groupable`. */\n defaultGroupBy?: string;\n pageSize?: number;\n pageSizeOptions?: number[];\n /** Adds the copy-first-value-downward column action. Defaults to true. */\n enableFillDown?: boolean;\n}\n\nexport interface PlateMapEditorHandle<T extends WellRecord = WellRecord> {\n /** Applies the staged record across the current selection. */\n apply: (scope?: PlateMapApplyScope) => void;\n /** Clears the current selection's wells. */\n clear: (scope?: PlateMapApplyScope) => void;\n /** Replaces the staged record. */\n setStaged: (next: Partial<T>) => void;\n /** Reads the staged record. */\n getStaged: () => Partial<T>;\n}\n\nexport interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends Omit<\n PlateMapActionsMenuProps,\n \"hasEntries\" | \"className\"\n> {\n format: PlateFormat;\n rows?: number;\n columns?: number;\n values: Map<WellId, T>;\n onChange: (next: Map<WellId, T>) => void;\n selection: Set<WellId>;\n onSelectionChange: (next: Set<WellId>) => void;\n\n fields: WellField<T>[];\n tableColumns: WellColumn<T>[];\n\n /**\n * Resolves the SVG fill color for a well. Optional — defaults to a filled\n * swatch for populated wells and the empty-well token for the rest, which is\n * enough for read-only or single-category views.\n */\n colorForWell?: (well: T | undefined, wellId: WellId) => string;\n /** Builds an empty record when a well is freshly created. Defaults to `{}`. */\n emptyEntry?: (wellId: WellId) => T;\n /**\n * Merges the staged form record onto an existing well record on Apply.\n * Defaults to a shallow merge (form keys overwrite existing keys when set).\n */\n mergeOnApply?: (existing: T | undefined, staged: Partial<T>, wellId: WellId) => T;\n /** Filter for the manifest's \"hide empty\" mode. */\n isPopulated?: (row: T) => boolean;\n /** Select field cycled when double-clicking a single well, e.g. role painting. */\n cycleFieldOnWellDoubleClick?: keyof T & string;\n /**\n * Names a `select` field whose options appear as a swatch strip anchored above\n * the selection, so a value can be painted across it in one click without a\n * trip to the form. Swatch colours come from each option's `swatch`.\n *\n * Applies immediately — this is a shortcut for the common case, not a staged\n * edit. Off unless set.\n */\n quickPaintField?: keyof T & string;\n\n /**\n * Controlled staged form record. Pair with `onStagedChange` to prefill the\n * form, read what's staged, or drive Apply from outside. When omitted the\n * editor owns staged state internally.\n */\n staged?: Partial<T>;\n onStagedChange?: (next: Partial<T>) => void;\n\n /** Optional header title (e.g. plate set name). */\n title?: string;\n /** Optional badges shown in the header next to the title. */\n badges?: React.ReactNode;\n /** Optional banner (e.g. import/error alert) shown above the whole layout. */\n banner?: React.ReactNode;\n /** Legend block. Position controlled by `legendPlacement`. */\n legend?: React.ReactNode;\n /**\n * Where `legend` renders. `\"form\"` (default) puts it under the form column,\n * separated by a divider. `\"plate\"` puts it under the grid inside the plate\n * card, stacked above anything in `plateFooter`. With `hideForm`, `\"form\"`\n * drops the legend entirely — use `\"plate\"` there.\n */\n legendPlacement?: \"form\" | \"plate\";\n /** Form helper slot rendered between fields and the action row. */\n formExtras?: React.ReactNode;\n /**\n * Fully replaces the left column. When set, the built-in `WellMetadataForm`\n * is omitted — use this for alternative workflows like a drag-and-drop\n * source palette. The legend slot still renders beneath the replacement.\n */\n formSlot?: React.ReactNode;\n /** Hides the form column entirely, leaving grid (+ manifest). */\n hideForm?: boolean;\n\n /* ---------------------------------------------------------------- Layout */\n\n /**\n * Where the metadata form sits relative to the plate grid. `\"start\"` /\n * `\"end\"` put it beside the grid (subject to `stackAt`); `\"top\"` / `\"bottom\"`\n * stack it full-width at every breakpoint. Defaults to `\"start\"`.\n */\n formPlacement?: PlateMapEditorFormPlacement;\n /**\n * Viewport width at which the form and grid sit side by side. Below it they\n * always stack full-width. Ignored when `formPlacement` is `\"top\"`/`\"bottom\"`.\n * Defaults to `\"md\"`.\n */\n stackAt?: PlateMapEditorStackAt;\n /**\n * Width of the form column at and above `stackAt`. Any CSS length (`\"420px\"`,\n * `\"28rem\"`, `\"30%\"`) or a number treated as px. Defaults to `\"360px\"`.\n * Below `stackAt` the form is always full-width regardless of this value.\n */\n formWidth?: number | string;\n /** Footer actions (e.g. Save, Back), rendered at the very bottom of the editor. */\n footer?: React.ReactNode;\n /** Title for the plate grid panel. */\n plateTitle?: React.ReactNode;\n /** Optional controls shown above the grid, inside the plate card. */\n plateToolbar?: React.ReactNode;\n /** Slot rendered above the grid's toolbar, scoped to the plate card only. */\n plateBanner?: React.ReactNode;\n /**\n * Content for the plate card's footer — the one slot below the grid. Renders\n * as a real `CardFooter`, so it picks up the card's footer styling. Use it\n * for per-plate actions or a summary; for the editor-wide action row use\n * `footer`.\n */\n plateFooter?: React.ReactNode;\n /** User-provided plates available for this editor. Barcodes are never generated by the editor. */\n plates?: PlateMapPlateOption[];\n activePlateId?: string;\n onPlateChange?: (plateId: string) => void;\n /** Opens the host app's manual barcode entry flow for creating a plate. */\n onAddPlate?: () => void;\n /** Removes a plate (typically when using `plateSelectorVariant=\"tabs\"`). */\n onRemovePlate?: (plateId: string) => void;\n addPlateLabel?: string;\n removePlateLabel?: string;\n plateSelectorLabel?: string;\n /** Layout of the plate selector. Defaults to `\"dropdown\"`. */\n plateSelectorVariant?: PlateMapPlateSelectorVariant;\n /** Row field used to stamp the active user-provided barcode onto edited wells. Defaults to `plateBarcode`. */\n plateBarcodeField?: keyof T & string;\n /**\n * Which plates Apply/Clear write to. `\"active-plate\"` (default) touches only\n * the plate on screen; `\"all-plates\"` writes the same well positions on every\n * plate in `plates`, each row stamped with its own barcode.\n *\n * For a UI offering both side by side, use `usePlateMapEditorState` directly —\n * `applyStagedToSelection(\"all-plates\")` takes a per-call override.\n */\n applyScope?: PlateMapApplyScope;\n /** Header for the automatic manifest barcode column. */\n plateBarcodeColumnHeader?: string;\n /** Hide the automatic manifest barcode column when plate-scoped editing is active. */\n hidePlateBarcodeColumn?: boolean;\n /** Optional grouped well shortcuts rendered under the grid. */\n groups?: PlateMapGroupOption[];\n activeGroupId?: string;\n onGroupClick?: (group: PlateMapGroupOption) => void;\n /** Custom hover summary for the strip above the grid. */\n renderHoverSummary?: (well: T | undefined, wellId: WellId) => React.ReactNode;\n /** Fixed well size. When unset, the grid grows with the available width. */\n cellSize?: number;\n /** Fill color for empty wells. Pass `null` to delegate empty wells to `colorForWell`. */\n emptyWellFillColor?: string | null;\n /** Well shape forwarded to `PlatePaintGrid`. Defaults to `\"rect\"`. */\n wellShape?: WellShape;\n /**\n * How a selected well is filled. `\"well\"` (default) keeps the well's own\n * colour and shows selection via the ring, so the result of an Apply is\n * visible while the wells are still selected. `\"selection\"` replaces the\n * fill with the selection tint.\n */\n selectionFillMode?: \"selection\" | \"well\";\n /** When true, wraps the grid in a card-like plate frame (rounded + border + soft shadow). */\n framedPlate?: boolean;\n /**\n * Forwarded to `PlatePaintGrid`. Render-prop that places a node inside each\n * absolute-positioned well cell — used to wire drop targets without binding\n * the kit to a specific DnD library.\n */\n wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;\n /** Wells to highlight (e.g. when hovering a legend item externally). */\n highlightedWellIds?: ReadonlySet<WellId>;\n /** Fires whenever the currently hovered well changes (null on leave). */\n onHoveredWellChange?: (wellId: WellId | null) => void;\n /** Hides the grid's built-in \"Select all\" / \"Deselect all\" links. */\n hideSelectionControls?: boolean;\n /**\n * Overrides every user-facing string the editor and its manifest render —\n * the single place to localise. Omitted keys fall back to English defaults.\n *\n * Does not cover `plateTitle` / `manifestTitle` (own props, `ReactNode`) or\n * the import/export menu's labels (`importCsvLabel` and friends, inherited\n * from `PlateMapActionsMenuProps`).\n */\n labels?: PlateMapEditorLabels;\n /** Hides the sample manifest panel entirely. */\n hideManifest?: boolean;\n /**\n * Fully replaces the manifest panel's body, keeping the card and heading.\n * Use for a bespoke summary table; pair with `hideManifest` to drop the\n * region altogether.\n */\n manifestSlot?: React.ReactNode;\n /** Heading for the manifest card. Defaults to `\"Sample manifest\"`. */\n manifestTitle?: React.ReactNode;\n /** Manifest table behaviour. See {@link PlateMapEditorManifestOptions}. */\n manifest?: PlateMapEditorManifestOptions;\n /** @deprecated Use `manifest={{ filterable }}`. */\n manifestFilterable?: boolean;\n /** @deprecated Use `manifest={{ groupable }}`. */\n manifestGroupable?: boolean;\n autoScaleGrid?: boolean;\n minCellSize?: number;\n maxCellSize?: number;\n\n /** Applied to the editor root. */\n className?: string;\n /** Inline styles for the editor root. */\n style?: React.CSSProperties;\n /** Per-region class hooks. See {@link PlateMapEditorClassNames}. */\n classNames?: PlateMapEditorClassNames;\n /**\n * Imperative handle for driving staged edits from outside the editor.\n * See {@link PlateMapEditorHandle}.\n */\n ref?: React.Ref<PlateMapEditorHandle<T>>;\n}\n\nfunction shouldShowPlateSelector(plateCount: number, onAddPlate: PlateMapEditorProps[\"onAddPlate\"]): boolean {\n return plateCount > 0 || !!onAddPlate;\n}\n\nfunction PlateMapEditorTitleBar({ title, badges }: { title?: string; badges?: React.ReactNode }) {\n if (!title && !badges) return null;\n\n return (\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n {title ? <h2 className=\"text-lg font-semibold\">{title}</h2> : <span />}\n {badges ? <div className=\"flex flex-wrap gap-2\">{badges}</div> : null}\n </div>\n );\n}\n\nfunction PlateMapEditorPlateSelectorSlot({\n visible,\n ...selectorProps\n}: { visible: boolean } & React.ComponentProps<typeof PlateMapPlateSelector>) {\n if (!visible) return null;\n return <PlateMapPlateSelector {...selectorProps} />;\n}\n\nfunction PlateMapEditorManifestCard({\n hidden,\n title,\n cardClassName,\n slot,\n manifest,\n}: {\n hidden: boolean;\n title: React.ReactNode;\n cardClassName?: string;\n slot?: React.ReactNode;\n manifest: React.ReactNode;\n}) {\n if (hidden) return null;\n\n return (\n <Card size=\"sm\" data-plate-map-region=\"manifest\" className={cn(\"w-full min-w-0\", cardClassName)}>\n {title ? (\n <CardHeader className=\"border-b\">\n <CardTitle>{title}</CardTitle>\n </CardHeader>\n ) : null}\n <CardContent className=\"min-w-0\">{slot ?? manifest}</CardContent>\n </Card>\n );\n}\n\n/**\n * Turnkey plate-map editing surface: a metadata form, an interactive plate\n * grid, and a sample manifest, wired together with the staged-edit controller\n * in {@link usePlateMapEditorState}.\n *\n * ## Choosing a level\n *\n * | You need | Use |\n * | --- | --- |\n * | The standard surface, tuned by props | `PlateMapEditor` (this) |\n * | A layout these props can't express | {@link usePlateMapEditorState} + `PlateMapForm` / `PlateMapGrid` / `PlateMapManifest` |\n * | One region only, wired yourself | The primitives directly |\n *\n * Dropping to the hook keeps the apply/clear semantics, plate scoping, and\n * barcode stamping — you only take over layout.\n *\n * ## Customization map\n *\n * - **Layout** — `formPlacement` (`start`/`end`/`top`/`bottom`), `stackAt`,\n * `formWidth`, `hideForm`, `hideManifest`.\n * - **Slots** — `title`, `badges`, `banner` (whole editor), `plateBanner`\n * (plate card only), `plateToolbar` (above grid), `plateFooter` (the plate\n * card's footer), `footer` (editor-wide action row), `legend` +\n * `legendPlacement`, `formExtras`, `formSlot` (replaces the form),\n * `manifestSlot` (replaces the manifest body).\n * - **Edit semantics** — `mergeOnApply`, `emptyEntry`, `isPopulated`,\n * `cycleFieldOnWellDoubleClick`, and `staged` / `onStagedChange` to make the\n * staged record controlled.\n * - **Labels** — one `labels` object covers every string the editor and its\n * manifest render. `plateTitle` / `manifestTitle` and the import/export menu\n * labels stay separate (they are `ReactNode` slots, not plain text).\n * - **Styling** — `className` / `style` on the root plus one `classNames` map\n * for the regions. Each card also carries\n * `data-plate-map-region=\"form|plate|manifest\"` for CSS targeting.\n * - **Manifest** — one `manifest` options object; `hideManifest`,\n * `manifestTitle` and `manifestSlot` stay top-level.\n *\n * ## Responsiveness\n *\n * The editor responds to **its container's** width, not the viewport's, so it\n * lays out correctly inside a narrow panel, split pane, or drawer on a wide\n * screen. `stackAt` names a container width (`sm` 640 / `md` 768 / `lg` 1024 /\n * `xl` 1280 / `never`); below it the form and grid stack full-width. A plate\n * too dense to fit scrolls inside its own container rather than widening the\n * page.\n *\n * @example Default surface\n * ```tsx\n * <PlateMapEditor\n * format=\"96\"\n * values={values}\n * onChange={setValues}\n * selection={selection}\n * onSelectionChange={setSelection}\n * fields={FIELDS}\n * tableColumns={COLUMNS}\n * />\n * ```\n *\n * @example Grid-only, form on the right, localised\n * ```tsx\n * <PlateMapEditor\n * {...base}\n * formPlacement=\"end\"\n * stackAt=\"lg\"\n * formWidth=\"22rem\"\n * hideManifest\n * labels={{ apply: \"Appliquer\", clearWells: \"Vider les puits\" }}\n * />\n * ```\n */\nexport function PlateMapEditor<T extends WellRecord = WellRecord>({\n format,\n rows,\n columns,\n values,\n onChange,\n selection,\n onSelectionChange,\n fields,\n tableColumns,\n colorForWell = defaultColorForWell,\n emptyEntry,\n mergeOnApply,\n isPopulated,\n cycleFieldOnWellDoubleClick,\n quickPaintField,\n staged: controlledStaged,\n onStagedChange,\n title,\n badges,\n banner,\n legend,\n legendPlacement = \"form\",\n formExtras,\n formSlot,\n hideForm = false,\n formPlacement = \"start\",\n stackAt = \"md\",\n formWidth = DEFAULT_FORM_WIDTH,\n footer,\n plateTitle = \"Plate\",\n plateToolbar,\n plateBanner,\n plateFooter,\n plates,\n activePlateId,\n onPlateChange,\n onAddPlate,\n onRemovePlate,\n addPlateLabel,\n removePlateLabel,\n plateSelectorLabel,\n plateSelectorVariant,\n plateBarcodeField,\n applyScope,\n plateBarcodeColumnHeader = DEFAULT_PLATE_BARCODE_HEADER,\n hidePlateBarcodeColumn = false,\n groups,\n activeGroupId,\n onGroupClick,\n renderHoverSummary,\n cellSize,\n emptyWellFillColor,\n wellShape,\n selectionFillMode,\n framedPlate,\n wrapWell,\n highlightedWellIds,\n onHoveredWellChange,\n hideSelectionControls,\n labels,\n hideManifest = false,\n manifestSlot,\n manifestTitle = DEFAULT_MANIFEST_TITLE,\n manifest,\n manifestFilterable,\n manifestGroupable,\n autoScaleGrid,\n minCellSize,\n maxCellSize,\n className,\n style,\n classNames,\n ref,\n templates,\n templateId,\n onTemplateChange,\n onClearTemplate,\n onImportCsv,\n onExportCsv,\n onImportTemplate,\n onExportTemplate,\n csvAccept,\n templateAccept,\n label,\n align,\n side,\n importTemplateLabel,\n exportTemplateLabel,\n importCsvLabel,\n exportCsvLabel,\n clearLabel,\n}: PlateMapEditorProps<T>) {\n const state = usePlateMapEditorState<T>({\n values,\n onChange,\n selection,\n onSelectionChange,\n emptyEntry,\n mergeOnApply,\n staged: controlledStaged,\n onStagedChange,\n fields,\n cycleFieldOnWellDoubleClick,\n plates,\n activePlateId,\n onPlateChange,\n plateBarcodeField,\n applyScope,\n onImportCsv,\n });\n\n React.useImperativeHandle(\n ref,\n () => ({\n apply: (scope?: PlateMapApplyScope) => state.applyStagedToSelection(scope),\n clear: (scope?: PlateMapApplyScope) => state.clearWells(scope),\n setStaged: (next: Partial<T>) => state.setStaged(next),\n getStaged: () => state.staged,\n }),\n [state],\n );\n\n // The two released flat props still win when set, so existing callers are\n // unaffected; everything else moved into the `manifest` object.\n const manifestOptions: PlateMapEditorManifestOptions = {\n ...manifest,\n filterable: manifestFilterable ?? manifest?.filterable,\n groupable: manifestGroupable ?? manifest?.groupable,\n };\n\n const barcodeField = (plateBarcodeField ?? DEFAULT_PLATE_BARCODE_FIELD) as keyof T & string;\n const activePlateBarcode = state.activePlate?.barcode;\n\n const manifestColumns = React.useMemo(() => {\n if (!state.isPlateScoped || !activePlateBarcode || hidePlateBarcodeColumn) return tableColumns;\n const alreadyHasBarcodeColumn = tableColumns.some(\n (column) => column.field === barcodeField || column.id === barcodeField,\n );\n if (alreadyHasBarcodeColumn) return tableColumns;\n\n const barcodeColumn: WellColumn<T> = {\n id: barcodeField,\n header: plateBarcodeColumnHeader,\n minWidth: 150,\n render: ({ row }) => {\n const barcode = row[barcodeField] ?? activePlateBarcode;\n return <Badge variant=\"outline\">{String(barcode)}</Badge>;\n },\n };\n return [barcodeColumn, ...tableColumns];\n }, [\n activePlateBarcode,\n barcodeField,\n hidePlateBarcodeColumn,\n state.isPlateScoped,\n plateBarcodeColumnHeader,\n tableColumns,\n ]);\n\n // The strip is dismissed once it has been used, and restored whenever the\n // selection changes — a new drag, or clicking the next cell. Painting changes\n // `values`, not `selection`, so a pick alone leaves it dismissed.\n const [quickPaintDismissed, setQuickPaintDismissed] = React.useState(false);\n React.useEffect(() => setQuickPaintDismissed(false), [selection]);\n\n // Anything that is not a well cell or the strip itself dismisses it — the\n // label gutter, the toolbar, elsewhere on the page. One rule rather than a\n // list of places, so there is no gap where a click leaves it stranded.\n // Clicks on a cell are excluded because they change the selection, which\n // restores the strip through the effect above.\n React.useEffect(() => {\n if (!quickPaintField) return;\n const onPointerDown = (event: PointerEvent) => {\n const target = event.target as Element | null;\n if (target?.closest?.(\"[data-well]\")) return;\n if (target?.closest?.('[data-slot=\"well-quick-paint\"]')) return;\n setQuickPaintDismissed(true);\n };\n document.addEventListener(\"pointerdown\", onPointerDown, true);\n return () => document.removeEventListener(\"pointerdown\", onPointerDown, true);\n }, [quickPaintField]);\n\n const quickPaint = React.useMemo(() => {\n if (!quickPaintField || selection.size === 0 || quickPaintDismissed) return;\n const field = fields.find((f) => f.key === quickPaintField);\n if (field?.kind !== \"select\" || !field.options?.length) return;\n\n // Show the current value only when every selected well agrees on it.\n let shared: string | undefined;\n let first = true;\n for (const wellId of selection) {\n const value = state.scopedValues.get(wellId)?.[quickPaintField];\n const asString = typeof value === \"string\" ? value : undefined;\n if (first) {\n shared = asString;\n first = false;\n } else if (shared !== asString) {\n shared = undefined;\n break;\n }\n }\n\n return (\n <WellQuickPaint\n options={field.options}\n activeValue={shared}\n selectionSize={selection.size}\n onPick={(value) => {\n state.applyToSelection({ [quickPaintField]: value } as Partial<T>);\n setQuickPaintDismissed(true);\n }}\n />\n );\n }, [fields, quickPaintDismissed, quickPaintField, selection, state]);\n\n const showPlateSelector = shouldShowPlateSelector(state.availablePlates.length, onAddPlate);\n\n // The legend rides with the grid when `legendPlacement=\"plate\"`, stacked\n // above whatever the caller put in `plateFooter`.\n const plateGridFooter = legendPlacement === \"plate\" && legend ? <div className=\"mt-3 border-t pt-3\">{legend}</div> : undefined;\n\n const isStackedPlacement = formPlacement === \"top\" || formPlacement === \"bottom\";\n const isFormFirst = formPlacement === \"start\" || formPlacement === \"top\";\n const resolvedFormWidth = typeof formWidth === \"number\" ? `${formWidth}px` : formWidth;\n\n const formColumn = hideForm ? null : (\n <Card\n key=\"form\"\n data-plate-map-region=\"form\"\n className={cn(\n // Full-width and free to shrink by default; the fixed column width only\n // applies from `stackAt` up, which is what keeps narrow viewports clean.\n \"flex w-full min-w-0 flex-col\",\n !isStackedPlacement && FORM_WIDTH_AT[stackAt],\n classNames?.formCard,\n )}\n size=\"sm\"\n >\n <CardContent className=\"flex h-full flex-1 flex-col gap-3\">\n <PlateMapForm\n fields={fields}\n value={state.staged}\n onChange={state.setStaged}\n selectionSize={selection.size}\n onApply={state.applyStagedToSelection}\n onClear={state.clearWells}\n applyLabel={labels?.apply}\n clearLabel={labels?.clearWells}\n selectionEmptyLabel={labels?.selectionEmpty}\n selectionCountLabel={labels?.selectionCount}\n extras={formExtras}\n legend={legendPlacement === \"form\" ? legend : undefined}\n formSlot={formSlot}\n className={classNames?.form}\n />\n </CardContent>\n </Card>\n );\n\n const plateColumn = (\n <Card\n key=\"plate\"\n data-plate-map-region=\"plate\"\n // `min-w-0` rather than a px floor: this flex child must be allowed to\n // shrink below its content width so the grid's own horizontal scroller\n // absorbs the overflow instead of the page doing it.\n className={cn(\"flex w-full min-w-0 flex-1 flex-col\", classNames?.plateCard)}\n size=\"sm\"\n >\n <CardHeader className=\"border-b\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-2\">\n <CardTitle className=\"min-w-0\">{plateTitle}</CardTitle>\n <PlateMapEditorPlateSelectorSlot\n visible={showPlateSelector}\n plates={state.availablePlates}\n activePlateId={state.activePlate?.id}\n onPlateChange={state.canChangePlate ? state.handlePlateChange : undefined}\n onAddPlate={onAddPlate}\n onRemovePlate={onRemovePlate}\n addPlateLabel={addPlateLabel}\n removePlateLabel={removePlateLabel}\n label={plateSelectorLabel}\n variant={plateSelectorVariant}\n />\n </div>\n <CardAction className=\"flex flex-wrap items-center gap-2\">\n <PlateMapActionsMenu\n templates={templates}\n templateId={templateId}\n onTemplateChange={onTemplateChange}\n onClearTemplate={onClearTemplate}\n hasEntries={values.size > 0}\n onImportCsv={onImportCsv ? state.handleImportCsv : undefined}\n onExportCsv={onExportCsv}\n onImportTemplate={onImportTemplate}\n onExportTemplate={onExportTemplate}\n csvAccept={csvAccept}\n templateAccept={templateAccept}\n label={label}\n align={align}\n side={side}\n importTemplateLabel={importTemplateLabel}\n exportTemplateLabel={exportTemplateLabel}\n importCsvLabel={importCsvLabel}\n exportCsvLabel={exportCsvLabel}\n clearLabel={clearLabel}\n />\n </CardAction>\n </CardHeader>\n <CardContent className=\"min-w-0\">\n <PlateMapGrid\n format={format}\n rows={rows}\n columns={columns}\n values={state.scopedValues}\n selection={selection}\n onSelectionChange={onSelectionChange}\n colorForWell={colorForWell}\n fields={fields}\n renderHoverSummary={renderHoverSummary}\n hoveredWellId={state.hoveredWellId}\n onHoveredWellChange={(wellId) => {\n state.setHoveredWellId(wellId);\n onHoveredWellChange?.(wellId);\n }}\n banner={plateBanner}\n toolbar={plateToolbar}\n footer={plateGridFooter}\n hideSelectionControls={hideSelectionControls}\n selectAllLabel={labels?.selectAll}\n deselectAllLabel={labels?.deselectAll}\n emptyWellFillColor={emptyWellFillColor}\n wellShape={wellShape}\n framed={framedPlate}\n wrapWell={wrapWell}\n quickPaint={quickPaint}\n highlightedWellIds={highlightedWellIds}\n onWellDoubleClick={state.cycleWellField}\n selectionFillMode={selectionFillMode}\n flashWellId={state.flashWell?.wellId}\n flashWellKey={state.flashWell?.key}\n cellSize={cellSize}\n autoScale={autoScaleGrid}\n minCellSize={minCellSize}\n maxCellSize={maxCellSize}\n groups={groups}\n activeGroupId={activeGroupId}\n onGroupClick={onGroupClick}\n className={classNames?.grid}\n />\n </CardContent>\n {plateFooter ? (\n <CardFooter className=\"flex flex-wrap items-center gap-2 border-t\">{plateFooter}</CardFooter>\n ) : null}\n </Card>\n );\n\n const columnsInOrder = isFormFirst ? [formColumn, plateColumn] : [plateColumn, formColumn];\n\n return (\n <div data-slot=\"plate-map-editor\" className={cn(\"flex w-full min-w-0 flex-col gap-4\", className)} style={style}>\n <PlateMapEditorTitleBar title={title} badges={badges} />\n\n {banner}\n\n {/*\n * `container-type` establishes a query context for DESCENDANTS — an\n * element cannot query its own width — so the `@container` marker and the\n * `@min-[…]` variants it drives must live on different elements. Querying\n * the container rather than the viewport is what lets the editor lay out\n * correctly inside a narrow panel on a wide screen.\n */}\n <div\n data-slot=\"plate-map-editor-container\"\n className=\"@container w-full min-w-0\"\n style={{ \"--plate-map-form-width\": resolvedFormWidth } as React.CSSProperties}\n >\n <div\n data-slot=\"plate-map-editor-layout\"\n // Mobile-first: a plain stacked column, promoted to a row only once\n // the container is wide enough (never, for top/bottom placement).\n className={cn(\n \"flex w-full min-w-0 flex-col gap-3\",\n !isStackedPlacement && SIDE_BY_SIDE_AT[stackAt],\n classNames?.layout,\n )}\n >\n {columnsInOrder}\n </div>\n </div>\n\n <PlateMapEditorManifestCard\n hidden={hideManifest}\n title={manifestTitle}\n cardClassName={classNames?.manifestCard}\n slot={manifestSlot}\n manifest={\n <PlateMapManifest\n values={state.scopedValues}\n onChange={state.commitScopedValues}\n columns={manifestColumns}\n fields={fields}\n selection={selection}\n onSelectionChange={onSelectionChange}\n emptyEntry={emptyEntry}\n isPopulated={isPopulated}\n filterable={manifestOptions.filterable}\n filterColumns={manifestOptions.filterColumns}\n groupable={manifestOptions.groupable}\n defaultGroupBy={manifestOptions.defaultGroupBy}\n pageSize={manifestOptions.pageSize}\n pageSizeOptions={manifestOptions.pageSizeOptions}\n enableFillDown={manifestOptions.enableFillDown}\n labels={labels}\n className={classNames?.manifest}\n />\n }\n />\n\n {footer ? <div className=\"flex flex-wrap justify-end gap-2 pt-2\">{footer}</div> : null}\n </div>\n );\n}\n\nexport { Badge as PlateBadge };\n"],"names":["DEFAULT_PLATE_BARCODE_FIELD","DEFAULT_PLATE_BARCODE_HEADER","DEFAULT_MANIFEST_TITLE","DEFAULT_FORM_WIDTH","SIDE_BY_SIDE_AT","FORM_WIDTH_AT","shouldShowPlateSelector","plateCount","onAddPlate","PlateMapEditorTitleBar","title","badges","jsxs","jsx","PlateMapEditorPlateSelectorSlot","visible","selectorProps","PlateMapPlateSelector","PlateMapEditorManifestCard","hidden","cardClassName","slot","manifest","Card","cn","CardHeader","CardTitle","CardContent","PlateMapEditor","format","rows","columns","values","onChange","selection","onSelectionChange","fields","tableColumns","colorForWell","defaultColorForWell","emptyEntry","mergeOnApply","isPopulated","cycleFieldOnWellDoubleClick","quickPaintField","controlledStaged","onStagedChange","banner","legend","legendPlacement","formExtras","formSlot","hideForm","formPlacement","stackAt","formWidth","footer","plateTitle","plateToolbar","plateBanner","plateFooter","plates","activePlateId","onPlateChange","onRemovePlate","addPlateLabel","removePlateLabel","plateSelectorLabel","plateSelectorVariant","plateBarcodeField","applyScope","plateBarcodeColumnHeader","hidePlateBarcodeColumn","groups","activeGroupId","onGroupClick","renderHoverSummary","cellSize","emptyWellFillColor","wellShape","selectionFillMode","framedPlate","wrapWell","highlightedWellIds","onHoveredWellChange","hideSelectionControls","labels","hideManifest","manifestSlot","manifestTitle","manifestFilterable","manifestGroupable","autoScaleGrid","minCellSize","maxCellSize","className","style","classNames","ref","templates","templateId","onTemplateChange","onClearTemplate","onImportCsv","onExportCsv","onImportTemplate","onExportTemplate","csvAccept","templateAccept","label","align","side","importTemplateLabel","exportTemplateLabel","importCsvLabel","exportCsvLabel","clearLabel","state","usePlateMapEditorState","React","scope","next","manifestOptions","barcodeField","activePlateBarcode","manifestColumns","column","row","barcode","Badge","quickPaintDismissed","setQuickPaintDismissed","onPointerDown","event","target","quickPaint","field","f","shared","first","wellId","value","asString","WellQuickPaint","showPlateSelector","plateGridFooter","isStackedPlacement","isFormFirst","resolvedFormWidth","formColumn","PlateMapForm","plateColumn","CardAction","PlateMapActionsMenu","PlateMapGrid","CardFooter","columnsInOrder","PlateMapManifest"],"mappings":";;;;;;;;;;;;;AA+BA,MAAMA,KAA8B,gBAC9BC,KAA+B,iBAC/BC,KAAyB,mBACzBC,KAAqB,SAuBrBC,KAAyD;AAAA,EAC7D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,OAAO;AACT,GAEMC,KAAuD;AAAA,EAC3D,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,OAAO;AACT;AA6RA,SAASC,GAAwBC,GAAoBC,GAAwD;AAC3G,SAAOD,IAAa,KAAK,CAAC,CAACC;AAC7B;AAEA,SAASC,GAAuB,EAAE,OAAAC,GAAO,QAAAC,KAAwD;AAC/F,SAAI,CAACD,KAAS,CAACC,IAAe,OAG5B,gBAAAC,EAAC,OAAA,EAAI,WAAU,qDACZ,UAAA;AAAA,IAAAF,sBAAS,MAAA,EAAG,WAAU,yBAAyB,UAAAA,GAAM,sBAAS,QAAA,CAAA,CAAK;AAAA,IACnEC,IAAS,gBAAAE,EAAC,OAAA,EAAI,WAAU,wBAAwB,aAAO,IAAS;AAAA,EAAA,GACnE;AAEJ;AAEA,SAASC,GAAgC;AAAA,EACvC,SAAAC;AAAA,EACA,GAAGC;AACL,GAA8E;AAC5E,SAAKD,IACE,gBAAAF,EAACI,IAAA,EAAuB,GAAGD,EAAA,CAAe,IAD5B;AAEvB;AAEA,SAASE,GAA2B;AAAA,EAClC,QAAAC;AAAA,EACA,OAAAT;AAAA,EACA,eAAAU;AAAA,EACA,MAAAC;AAAA,EACA,UAAAC;AACF,GAMG;AACD,SAAIH,IAAe,OAGjB,gBAAAP,EAACW,GAAA,EAAK,MAAK,MAAK,yBAAsB,YAAW,WAAWC,EAAG,kBAAkBJ,CAAa,GAC3F,UAAA;AAAA,IAAAV,IACC,gBAAAG,EAACY,KAAW,WAAU,YACpB,4BAACC,GAAA,EAAW,UAAAhB,EAAA,CAAM,GACpB,IACE;AAAA,IACJ,gBAAAG,EAACc,GAAA,EAAY,WAAU,WAAW,eAAQL,EAAA,CAAS;AAAA,EAAA,GACrD;AAEJ;AAyEO,SAASM,GAAkD;AAAA,EAChE,QAAAC;AAAA,EACA,MAAAC;AAAA,EACA,SAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,WAAAC;AAAA,EACA,mBAAAC;AAAA,EACA,QAAAC;AAAA,EACA,cAAAC;AAAA,EACA,cAAAC,IAAeC;AAAA,EACf,YAAAC;AAAA,EACA,cAAAC;AAAA,EACA,aAAAC;AAAA,EACA,6BAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,QAAQC;AAAA,EACR,gBAAAC;AAAA,EACA,OAAApC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAoC;AAAA,EACA,QAAAC;AAAA,EACA,iBAAAC,IAAkB;AAAA,EAClB,YAAAC;AAAA,EACA,UAAAC;AAAA,EACA,UAAAC,KAAW;AAAA,EACX,eAAAC,IAAgB;AAAA,EAChB,SAAAC,IAAU;AAAA,EACV,WAAAC,IAAYpD;AAAA,EACZ,QAAAqD;AAAA,EACA,YAAAC,KAAa;AAAA,EACb,cAAAC;AAAA,EACA,aAAAC;AAAA,EACA,aAAAC;AAAA,EACA,QAAAC;AAAA,EACA,eAAAC;AAAA,EACA,eAAAC;AAAA,EACA,YAAAvD;AAAA,EACA,eAAAwD;AAAA,EACA,eAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,oBAAAC;AAAA,EACA,sBAAAC;AAAA,EACA,mBAAAC;AAAA,EACA,YAAAC;AAAA,EACA,0BAAAC,IAA2BtE;AAAA,EAC3B,wBAAAuE,IAAyB;AAAA,EACzB,QAAAC;AAAA,EACA,eAAAC;AAAA,EACA,cAAAC;AAAA,EACA,oBAAAC;AAAA,EACA,UAAAC;AAAA,EACA,oBAAAC;AAAA,EACA,WAAAC;AAAA,EACA,mBAAAC;AAAA,EACA,aAAAC;AAAA,EACA,UAAAC;AAAA,EACA,oBAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,uBAAAC;AAAA,EACA,QAAAC;AAAA,EACA,cAAAC,KAAe;AAAA,EACf,cAAAC;AAAA,EACA,eAAAC,KAAgBvF;AAAA,EAChB,UAAAoB;AAAA,EACA,oBAAAoE;AAAA,EACA,mBAAAC;AAAA,EACA,eAAAC;AAAA,EACA,aAAAC;AAAA,EACA,aAAAC;AAAA,EACA,WAAAC;AAAA,EACA,OAAAC;AAAA,EACA,YAAAC;AAAA,EACA,KAAAC;AAAA,EACA,WAAAC;AAAA,EACA,YAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,aAAAC;AAAA,EACA,aAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,kBAAAC;AAAA,EACA,WAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,OAAAC;AAAA,EACA,OAAAC;AAAA,EACA,MAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,gBAAAC;AAAA,EACA,YAAAC;AACF,GAA2B;AACzB,QAAMC,IAAQC,GAA0B;AAAA,IACtC,QAAAtF;AAAA,IACA,UAAAC;AAAA,IACA,WAAAC;AAAA,IACA,mBAAAC;AAAA,IACA,YAAAK;AAAA,IACA,cAAAC;AAAA,IACA,QAAQI;AAAA,IACR,gBAAAC;AAAA,IACA,QAAAV;AAAA,IACA,6BAAAO;AAAA,IACA,QAAAkB;AAAA,IACA,eAAAC;AAAA,IACA,eAAAC;AAAA,IACA,mBAAAM;AAAA,IACA,YAAAC;AAAA,IACA,aAAAiC;AAAA,EAAA,CACD;AAED,EAAAgB,EAAM;AAAA,IACJrB;AAAA,IACA,OAAO;AAAA,MACL,OAAO,CAACsB,MAA+BH,EAAM,uBAAuBG,CAAK;AAAA,MACzE,OAAO,CAACA,MAA+BH,EAAM,WAAWG,CAAK;AAAA,MAC7D,WAAW,CAACC,MAAqBJ,EAAM,UAAUI,CAAI;AAAA,MACrD,WAAW,MAAMJ,EAAM;AAAA,IAAA;AAAA,IAEzB,CAACA,CAAK;AAAA,EAAA;AAKR,QAAMK,IAAiD;AAAA,IACrD,GAAGpG;AAAA,IACH,YAAYoE,MAAsBpE,GAAU;AAAA,IAC5C,WAAWqE,MAAqBrE,GAAU;AAAA,EAAA,GAGtCqG,IAAgBtD,KAAqBrE,IACrC4H,IAAqBP,EAAM,aAAa,SAExCQ,KAAkBN,EAAM,QAAQ,MAChC,CAACF,EAAM,iBAAiB,CAACO,KAAsBpD,KACnBnC,EAAa;AAAA,IAC3C,CAACyF,MAAWA,EAAO,UAAUH,KAAgBG,EAAO,OAAOH;AAAA,EAAA,IAEzBtF,IAW7B,CAT8B;AAAA,IACnC,IAAIsF;AAAA,IACJ,QAAQpD;AAAA,IACR,UAAU;AAAA,IACV,QAAQ,CAAC,EAAE,KAAAwD,QAAU;AACnB,YAAMC,IAAUD,EAAIJ,CAAY,KAAKC;AACrC,+BAAQK,IAAA,EAAM,SAAQ,WAAW,UAAA,OAAOD,CAAO,GAAE;AAAA,IACnD;AAAA,EAAA,GAEqB,GAAG3F,CAAY,GACrC;AAAA,IACDuF;AAAA,IACAD;AAAA,IACAnD;AAAA,IACA6C,EAAM;AAAA,IACN9C;AAAA,IACAlC;AAAA,EAAA,CACD,GAKK,CAAC6F,GAAqBC,CAAsB,IAAIZ,EAAM,SAAS,EAAK;AAC1E,EAAAA,EAAM,UAAU,MAAMY,EAAuB,EAAK,GAAG,CAACjG,CAAS,CAAC,GAOhEqF,EAAM,UAAU,MAAM;AACpB,QAAI,CAAC3E,EAAiB;AACtB,UAAMwF,IAAgB,CAACC,MAAwB;AAC7C,YAAMC,IAASD,EAAM;AACrB,MAAIC,GAAQ,UAAU,aAAa,KAC/BA,GAAQ,UAAU,gCAAgC,KACtDH,EAAuB,EAAI;AAAA,IAC7B;AACA,oBAAS,iBAAiB,eAAeC,GAAe,EAAI,GACrD,MAAM,SAAS,oBAAoB,eAAeA,GAAe,EAAI;AAAA,EAC9E,GAAG,CAACxF,CAAe,CAAC;AAEpB,QAAM2F,KAAahB,EAAM,QAAQ,MAAM;AACrC,QAAI,CAAC3E,KAAmBV,EAAU,SAAS,KAAKgG,EAAqB;AACrE,UAAMM,IAAQpG,EAAO,KAAK,CAACqG,MAAMA,EAAE,QAAQ7F,CAAe;AAC1D,QAAI4F,GAAO,SAAS,YAAY,CAACA,EAAM,SAAS,OAAQ;AAGxD,QAAIE,GACAC,IAAQ;AACZ,eAAWC,KAAU1G,GAAW;AAC9B,YAAM2G,IAAQxB,EAAM,aAAa,IAAIuB,CAAM,IAAIhG,CAAe,GACxDkG,IAAW,OAAOD,KAAU,WAAWA,IAAQ;AACrD,UAAIF;AACF,QAAAD,IAASI,GACTH,IAAQ;AAAA,eACCD,MAAWI,GAAU;AAC9B,QAAAJ,IAAS;AACT;AAAA,MACF;AAAA,IACF;AAEA,WACE,gBAAA7H;AAAA,MAACkI;AAAA,MAAA;AAAA,QACC,SAASP,EAAM;AAAA,QACf,aAAaE;AAAA,QACb,eAAexG,EAAU;AAAA,QACzB,QAAQ,CAAC2G,MAAU;AACjB,UAAAxB,EAAM,iBAAiB,EAAE,CAACzE,CAAe,GAAGiG,GAAqB,GACjEV,EAAuB,EAAI;AAAA,QAC7B;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN,GAAG,CAAC/F,GAAQ8F,GAAqBtF,GAAiBV,GAAWmF,CAAK,CAAC,GAE7D2B,KAAoB1I,GAAwB+G,EAAM,gBAAgB,QAAQ7G,CAAU,GAIpFyI,KAAkBhG,MAAoB,WAAWD,sBAAU,OAAA,EAAI,WAAU,sBAAsB,UAAAA,EAAA,CAAO,IAAS,QAE/GkG,IAAqB7F,MAAkB,SAASA,MAAkB,UAClE8F,KAAc9F,MAAkB,WAAWA,MAAkB,OAC7D+F,KAAoB,OAAO7F,KAAc,WAAW,GAAGA,CAAS,OAAOA,GAEvE8F,IAAajG,KAAW,OAC5B,gBAAAvC;AAAA,IAACU;AAAA,IAAA;AAAA,MAEC,yBAAsB;AAAA,MACtB,WAAWC;AAAA;AAAA;AAAA,QAGT;AAAA,QACA,CAAC0H,KAAsB7I,GAAciD,CAAO;AAAA,QAC5C2C,GAAY;AAAA,MAAA;AAAA,MAEd,MAAK;AAAA,MAEL,UAAA,gBAAApF,EAACc,GAAA,EAAY,WAAU,qCACrB,UAAA,gBAAAd;AAAA,QAACyI;AAAA,QAAA;AAAA,UACC,QAAAlH;AAAA,UACA,OAAOiF,EAAM;AAAA,UACb,UAAUA,EAAM;AAAA,UAChB,eAAenF,EAAU;AAAA,UACzB,SAASmF,EAAM;AAAA,UACf,SAASA,EAAM;AAAA,UACf,YAAY/B,GAAQ;AAAA,UACpB,YAAYA,GAAQ;AAAA,UACpB,qBAAqBA,GAAQ;AAAA,UAC7B,qBAAqBA,GAAQ;AAAA,UAC7B,QAAQpC;AAAA,UACR,QAAQD,MAAoB,SAASD,IAAS;AAAA,UAC9C,UAAAG;AAAA,UACA,WAAW8C,GAAY;AAAA,QAAA;AAAA,MAAA,EACzB,CACF;AAAA,IAAA;AAAA,IA5BI;AAAA,EAAA,GAgCFsD,IACJ,gBAAA3I;AAAA,IAACW;AAAA,IAAA;AAAA,MAEC,yBAAsB;AAAA,MAItB,WAAWC,EAAG,uCAAuCyE,GAAY,SAAS;AAAA,MAC1E,MAAK;AAAA,MAEL,UAAA;AAAA,QAAA,gBAAArF,EAACa,GAAA,EAAW,WAAU,YACpB,UAAA;AAAA,UAAA,gBAAAb,EAAC,OAAA,EAAI,WAAU,6CACb,UAAA;AAAA,YAAA,gBAAAC,EAACa,GAAA,EAAU,WAAU,WAAW,UAAA+B,IAAW;AAAA,YAC3C,gBAAA5C;AAAA,cAACC;AAAA,cAAA;AAAA,gBACC,SAASkI;AAAA,gBACT,QAAQ3B,EAAM;AAAA,gBACd,eAAeA,EAAM,aAAa;AAAA,gBAClC,eAAeA,EAAM,iBAAiBA,EAAM,oBAAoB;AAAA,gBAChE,YAAA7G;AAAA,gBACA,eAAAwD;AAAA,gBACA,eAAAC;AAAA,gBACA,kBAAAC;AAAA,gBACA,OAAOC;AAAA,gBACP,SAASC;AAAA,cAAA;AAAA,YAAA;AAAA,UACX,GACF;AAAA,UACA,gBAAAvD,EAAC2I,IAAA,EAAW,WAAU,qCACpB,UAAA,gBAAA3I;AAAA,YAAC4I;AAAA,YAAA;AAAA,cACC,WAAAtD;AAAA,cACA,YAAAC;AAAA,cACA,kBAAAC;AAAA,cACA,iBAAAC;AAAA,cACA,YAAYtE,EAAO,OAAO;AAAA,cAC1B,aAAauE,IAAcc,EAAM,kBAAkB;AAAA,cACnD,aAAAb;AAAA,cACA,kBAAAC;AAAA,cACA,kBAAAC;AAAA,cACA,WAAAC;AAAA,cACA,gBAAAC;AAAA,cACA,OAAAC;AAAA,cACA,OAAAC;AAAA,cACA,MAAAC;AAAA,cACA,qBAAAC;AAAA,cACA,qBAAAC;AAAA,cACA,gBAAAC;AAAA,cACA,gBAAAC;AAAA,cACA,YAAAC;AAAA,YAAA;AAAA,UAAA,EACF,CACF;AAAA,QAAA,GACF;AAAA,QACA,gBAAAvG,EAACc,GAAA,EAAY,WAAU,WACrB,UAAA,gBAAAd;AAAA,UAAC6I;AAAA,UAAA;AAAA,YACC,QAAA7H;AAAA,YACA,MAAAC;AAAA,YACA,SAAAC;AAAA,YACA,QAAQsF,EAAM;AAAA,YACd,WAAAnF;AAAA,YACA,mBAAAC;AAAA,YACA,cAAAG;AAAA,YACA,QAAAF;AAAA,YACA,oBAAAwC;AAAA,YACA,eAAeyC,EAAM;AAAA,YACrB,qBAAqB,CAACuB,MAAW;AAC/B,cAAAvB,EAAM,iBAAiBuB,CAAM,GAC7BxD,KAAsBwD,CAAM;AAAA,YAC9B;AAAA,YACA,QAAQjF;AAAA,YACR,SAASD;AAAA,YACT,QAAQuF;AAAA,YACR,uBAAA5D;AAAA,YACA,gBAAgBC,GAAQ;AAAA,YACxB,kBAAkBA,GAAQ;AAAA,YAC1B,oBAAAR;AAAA,YACA,WAAAC;AAAA,YACA,QAAQE;AAAA,YACR,UAAAC;AAAA,YACA,YAAAqD;AAAA,YACA,oBAAApD;AAAA,YACA,mBAAmBkC,EAAM;AAAA,YACzB,mBAAArC;AAAA,YACA,aAAaqC,EAAM,WAAW;AAAA,YAC9B,cAAcA,EAAM,WAAW;AAAA,YAC/B,UAAAxC;AAAA,YACA,WAAWe;AAAA,YACX,aAAAC;AAAA,YACA,aAAAC;AAAA,YACA,QAAArB;AAAA,YACA,eAAAC;AAAA,YACA,cAAAC;AAAA,YACA,WAAWsB,GAAY;AAAA,UAAA;AAAA,QAAA,GAE3B;AAAA,QACCrC,IACC,gBAAA/C,EAAC8I,IAAA,EAAW,WAAU,8CAA8C,aAAY,IAC9E;AAAA,MAAA;AAAA,IAAA;AAAA,IA5FA;AAAA,EAAA,GAgGFC,KAAiBT,KAAc,CAACE,GAAYE,CAAW,IAAI,CAACA,GAAaF,CAAU;AAEzF,SACE,gBAAAzI,EAAC,SAAI,aAAU,oBAAmB,WAAWY,EAAG,sCAAsCuE,EAAS,GAAG,OAAAC,IAChG,UAAA;AAAA,IAAA,gBAAAnF,EAACJ,IAAA,EAAuB,OAAAC,IAAc,QAAAC,GAAA,CAAgB;AAAA,IAErDoC;AAAA,IASD,gBAAAlC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAU;AAAA,QACV,OAAO,EAAE,0BAA0BuI,GAAA;AAAA,QAEnC,UAAA,gBAAAvI;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,aAAU;AAAA,YAGV,WAAWW;AAAA,cACT;AAAA,cACA,CAAC0H,KAAsB9I,GAAgBkD,CAAO;AAAA,cAC9C2C,GAAY;AAAA,YAAA;AAAA,YAGb,UAAA2D;AAAA,UAAA;AAAA,QAAA;AAAA,MACH;AAAA,IAAA;AAAA,IAGF,gBAAA/I;AAAA,MAACK;AAAA,MAAA;AAAA,QACC,QAAQqE;AAAA,QACR,OAAOE;AAAA,QACP,eAAeQ,GAAY;AAAA,QAC3B,MAAMT;AAAA,QACN,UACE,gBAAA3E;AAAA,UAACgJ;AAAA,UAAA;AAAA,YACC,QAAQxC,EAAM;AAAA,YACd,UAAUA,EAAM;AAAA,YAChB,SAASQ;AAAA,YACT,QAAAzF;AAAA,YACA,WAAAF;AAAA,YACA,mBAAAC;AAAA,YACA,YAAAK;AAAA,YACA,aAAAE;AAAA,YACA,YAAYgF,EAAgB;AAAA,YAC5B,eAAeA,EAAgB;AAAA,YAC/B,WAAWA,EAAgB;AAAA,YAC3B,gBAAgBA,EAAgB;AAAA,YAChC,UAAUA,EAAgB;AAAA,YAC1B,iBAAiBA,EAAgB;AAAA,YACjC,gBAAgBA,EAAgB;AAAA,YAChC,QAAApC;AAAA,YACA,WAAWW,GAAY;AAAA,UAAA;AAAA,QAAA;AAAA,MACzB;AAAA,IAAA;AAAA,IAIHzC,IAAS,gBAAA3C,EAAC,OAAA,EAAI,WAAU,yCAAyC,aAAO,IAAS;AAAA,EAAA,GACpF;AAEJ;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),d=require("./WellMetadataForm.cjs"),x=require("../../../ui/separator.cjs"),j=require("../../../lib/utils.cjs");function f({fields:t,value:a,onChange:l,selectionSize:o,onApply:s,onClear:i,applyLabel:n,clearLabel:u,extras:c,legend:r,formSlot:m,className:p}){return e.jsxs("div",{"data-slot":"plate-map-form",className:j.cn("flex flex-col gap-3",p),children:[m??e.jsx(d.WellMetadataForm,{fields:t,value:a,onChange:l,selectionSize:o,onApply:s,onClear:i,applyLabel:n,clearLabel:u,extras:c}),r?e.jsxs(e.Fragment,{children:[e.jsx(x.Separator,{}),r]}):null]})}exports.PlateMapForm=f;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),j=require("./WellMetadataForm.cjs"),f=require("../../../ui/separator.cjs"),F=require("../../../lib/utils.cjs");function M({fields:t,value:a,onChange:l,selectionSize:o,onApply:s,onClear:i,applyLabel:n,clearLabel:u,selectionEmptyLabel:c,selectionCountLabel:m,extras:p,legend:r,formSlot:d,className:x}){return e.jsxs("div",{"data-slot":"plate-map-form",className:F.cn("flex flex-col gap-3",x),children:[d??e.jsx(j.WellMetadataForm,{fields:t,value:a,onChange:l,selectionSize:o,onApply:s,onClear:i,applyLabel:n,clearLabel:u,selectionEmptyLabel:c,selectionCountLabel:m,extras:p}),r?e.jsxs(e.Fragment,{children:[e.jsx(f.Separator,{}),r]}):null]})}exports.PlateMapForm=M;
2
2
  //# sourceMappingURL=PlateMapForm.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlateMapForm.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapForm.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { WellMetadataForm } from \"./WellMetadataForm\";\n\nimport type { WellField, WellRecord } from \"./types\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateMapFormProps<T extends WellRecord = WellRecord> {\n fields: WellField<T>[];\n /** Staged values applied across the current selection on Apply. */\n value: Partial<T>;\n onChange: (next: Partial<T>) => void;\n selectionSize: number;\n onApply: () => void;\n onClear: () => void;\n applyLabel?: string;\n clearLabel?: string;\n /** Helper slot rendered between the fields and the action row. */\n extras?: React.ReactNode;\n /** Legend block rendered beneath the form, separated by a divider. */\n legend?: React.ReactNode;\n /**\n * Fully replaces the built-in `WellMetadataForm` — e.g. a drag-and-drop\n * source palette. The legend slot still renders beneath the replacement.\n */\n formSlot?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Card-agnostic metadata form panel. Renders the staged-value form (or a\n * caller-supplied `formSlot`) plus an optional legend. Bring your own\n * container — drop it bare into a sidebar, wrap it in a `Card`, or stack it\n * above the grid.\n */\nexport function PlateMapForm<T extends WellRecord = WellRecord>({\n fields,\n value,\n onChange,\n selectionSize,\n onApply,\n onClear,\n applyLabel,\n clearLabel,\n extras,\n legend,\n formSlot,\n className,\n}: PlateMapFormProps<T>) {\n return (\n <div data-slot=\"plate-map-form\" className={cn(\"flex flex-col gap-3\", className)}>\n {formSlot ?? (\n <WellMetadataForm<T>\n fields={fields}\n value={value}\n onChange={onChange}\n selectionSize={selectionSize}\n onApply={onApply}\n onClear={onClear}\n applyLabel={applyLabel}\n clearLabel={clearLabel}\n extras={extras}\n />\n )}\n {legend ? (\n <>\n <Separator />\n {legend}\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["PlateMapForm","fields","value","onChange","selectionSize","onApply","onClear","applyLabel","clearLabel","extras","legend","formSlot","className","jsxs","cn","jsx","WellMetadataForm","Fragment","Separator"],"mappings":"oOAqCO,SAASA,EAAgD,CAC9D,OAAAC,EACA,MAAAC,EACA,SAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EACA,WAAAC,EACA,WAAAC,EACA,OAAAC,EACA,OAAAC,EACA,SAAAC,EACA,UAAAC,CACF,EAAyB,CACvB,OACEC,OAAC,OAAI,YAAU,iBAAiB,UAAWC,EAAAA,GAAG,sBAAuBF,CAAS,EAC3E,SAAA,CAAAD,GACCI,EAAAA,IAACC,EAAAA,iBAAA,CACC,OAAAf,EACA,MAAAC,EACA,SAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EACA,WAAAC,EACA,WAAAC,EACA,OAAAC,CAAA,CAAA,EAGHC,EACCG,EAAAA,KAAAI,WAAA,CACE,SAAA,CAAAF,EAAAA,IAACG,EAAAA,UAAA,EAAU,EACVR,CAAA,CAAA,CACH,EACE,IAAA,EACN,CAEJ"}
1
+ {"version":3,"file":"PlateMapForm.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapForm.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { WellMetadataForm } from \"./WellMetadataForm\";\n\nimport type { WellField, WellRecord } from \"./types\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateMapFormProps<T extends WellRecord = WellRecord> {\n fields: WellField<T>[];\n /** Staged values applied across the current selection on Apply. */\n value: Partial<T>;\n onChange: (next: Partial<T>) => void;\n selectionSize: number;\n onApply: () => void;\n onClear: () => void;\n applyLabel?: string;\n clearLabel?: string;\n /** Heading shown when nothing is selected. */\n selectionEmptyLabel?: string;\n /** Heading shown with a selection. */\n selectionCountLabel?: (selectionSize: number) => string;\n /** Helper slot rendered between the fields and the action row. */\n extras?: React.ReactNode;\n /** Legend block rendered beneath the form, separated by a divider. */\n legend?: React.ReactNode;\n /**\n * Fully replaces the built-in `WellMetadataForm` — e.g. a drag-and-drop\n * source palette. The legend slot still renders beneath the replacement.\n */\n formSlot?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Card-agnostic metadata form panel. Renders the staged-value form (or a\n * caller-supplied `formSlot`) plus an optional legend. Bring your own\n * container — drop it bare into a sidebar, wrap it in a `Card`, or stack it\n * above the grid.\n */\nexport function PlateMapForm<T extends WellRecord = WellRecord>({\n fields,\n value,\n onChange,\n selectionSize,\n onApply,\n onClear,\n applyLabel,\n clearLabel,\n selectionEmptyLabel,\n selectionCountLabel,\n extras,\n legend,\n formSlot,\n className,\n}: PlateMapFormProps<T>) {\n return (\n <div data-slot=\"plate-map-form\" className={cn(\"flex flex-col gap-3\", className)}>\n {formSlot ?? (\n <WellMetadataForm<T>\n fields={fields}\n value={value}\n onChange={onChange}\n selectionSize={selectionSize}\n onApply={onApply}\n onClear={onClear}\n applyLabel={applyLabel}\n clearLabel={clearLabel}\n selectionEmptyLabel={selectionEmptyLabel}\n selectionCountLabel={selectionCountLabel}\n extras={extras}\n />\n )}\n {legend ? (\n <>\n <Separator />\n {legend}\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["PlateMapForm","fields","value","onChange","selectionSize","onApply","onClear","applyLabel","clearLabel","selectionEmptyLabel","selectionCountLabel","extras","legend","formSlot","className","jsxs","cn","jsx","WellMetadataForm","Fragment","Separator"],"mappings":"oOAyCO,SAASA,EAAgD,CAC9D,OAAAC,EACA,MAAAC,EACA,SAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EACA,WAAAC,EACA,WAAAC,EACA,oBAAAC,EACA,oBAAAC,EACA,OAAAC,EACA,OAAAC,EACA,SAAAC,EACA,UAAAC,CACF,EAAyB,CACvB,OACEC,OAAC,OAAI,YAAU,iBAAiB,UAAWC,EAAAA,GAAG,sBAAuBF,CAAS,EAC3E,SAAA,CAAAD,GACCI,EAAAA,IAACC,EAAAA,iBAAA,CACC,OAAAjB,EACA,MAAAC,EACA,SAAAC,EACA,cAAAC,EACA,QAAAC,EACA,QAAAC,EACA,WAAAC,EACA,WAAAC,EACA,oBAAAC,EACA,oBAAAC,EACA,OAAAC,CAAA,CAAA,EAGHC,EACCG,EAAAA,KAAAI,WAAA,CACE,SAAA,CAAAF,EAAAA,IAACG,EAAAA,UAAA,EAAU,EACVR,CAAA,CAAA,CACH,EACE,IAAA,EACN,CAEJ"}
@@ -10,6 +10,10 @@ export interface PlateMapFormProps<T extends WellRecord = WellRecord> {
10
10
  onClear: () => void;
11
11
  applyLabel?: string;
12
12
  clearLabel?: string;
13
+ /** Heading shown when nothing is selected. */
14
+ selectionEmptyLabel?: string;
15
+ /** Heading shown with a selection. */
16
+ selectionCountLabel?: (selectionSize: number) => string;
13
17
  /** Helper slot rendered between the fields and the action row. */
14
18
  extras?: React.ReactNode;
15
19
  /** Legend block rendered beneath the form, separated by a divider. */
@@ -27,4 +31,4 @@ export interface PlateMapFormProps<T extends WellRecord = WellRecord> {
27
31
  * container — drop it bare into a sidebar, wrap it in a `Card`, or stack it
28
32
  * above the grid.
29
33
  */
30
- export declare function PlateMapForm<T extends WellRecord = WellRecord>({ fields, value, onChange, selectionSize, onApply, onClear, applyLabel, clearLabel, extras, legend, formSlot, className, }: PlateMapFormProps<T>): import("react/jsx-runtime").JSX.Element;
34
+ export declare function PlateMapForm<T extends WellRecord = WellRecord>({ fields, value, onChange, selectionSize, onApply, onClear, applyLabel, clearLabel, selectionEmptyLabel, selectionCountLabel, extras, legend, formSlot, className, }: PlateMapFormProps<T>): import("react/jsx-runtime").JSX.Element;
@@ -1,8 +1,8 @@
1
- import { jsxs as o, jsx as a, Fragment as d } from "react/jsx-runtime";
2
- import { WellMetadataForm as u } from "./WellMetadataForm.js";
3
- import { Separator as F } from "../../../ui/separator.js";
4
- import { cn as h } from "../../../lib/utils.js";
5
- function N({
1
+ import { jsxs as o, jsx as a, Fragment as F } from "react/jsx-runtime";
2
+ import { WellMetadataForm as h } from "./WellMetadataForm.js";
3
+ import { Separator as j } from "../../../ui/separator.js";
4
+ import { cn as M } from "../../../lib/utils.js";
5
+ function S({
6
6
  fields: t,
7
7
  value: m,
8
8
  onChange: l,
@@ -11,14 +11,16 @@ function N({
11
11
  onClear: f,
12
12
  applyLabel: i,
13
13
  clearLabel: n,
14
- extras: c,
14
+ selectionEmptyLabel: c,
15
+ selectionCountLabel: s,
16
+ extras: x,
15
17
  legend: r,
16
- formSlot: s,
17
- className: x
18
+ formSlot: d,
19
+ className: u
18
20
  }) {
19
- return /* @__PURE__ */ o("div", { "data-slot": "plate-map-form", className: h("flex flex-col gap-3", x), children: [
20
- s ?? /* @__PURE__ */ a(
21
- u,
21
+ return /* @__PURE__ */ o("div", { "data-slot": "plate-map-form", className: M("flex flex-col gap-3", u), children: [
22
+ d ?? /* @__PURE__ */ a(
23
+ h,
22
24
  {
23
25
  fields: t,
24
26
  value: m,
@@ -28,16 +30,18 @@ function N({
28
30
  onClear: f,
29
31
  applyLabel: i,
30
32
  clearLabel: n,
31
- extras: c
33
+ selectionEmptyLabel: c,
34
+ selectionCountLabel: s,
35
+ extras: x
32
36
  }
33
37
  ),
34
- r ? /* @__PURE__ */ o(d, { children: [
35
- /* @__PURE__ */ a(F, {}),
38
+ r ? /* @__PURE__ */ o(F, { children: [
39
+ /* @__PURE__ */ a(j, {}),
36
40
  r
37
41
  ] }) : null
38
42
  ] });
39
43
  }
40
44
  export {
41
- N as PlateMapForm
45
+ S as PlateMapForm
42
46
  };
43
47
  //# sourceMappingURL=PlateMapForm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlateMapForm.js","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapForm.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { WellMetadataForm } from \"./WellMetadataForm\";\n\nimport type { WellField, WellRecord } from \"./types\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateMapFormProps<T extends WellRecord = WellRecord> {\n fields: WellField<T>[];\n /** Staged values applied across the current selection on Apply. */\n value: Partial<T>;\n onChange: (next: Partial<T>) => void;\n selectionSize: number;\n onApply: () => void;\n onClear: () => void;\n applyLabel?: string;\n clearLabel?: string;\n /** Helper slot rendered between the fields and the action row. */\n extras?: React.ReactNode;\n /** Legend block rendered beneath the form, separated by a divider. */\n legend?: React.ReactNode;\n /**\n * Fully replaces the built-in `WellMetadataForm` — e.g. a drag-and-drop\n * source palette. The legend slot still renders beneath the replacement.\n */\n formSlot?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Card-agnostic metadata form panel. Renders the staged-value form (or a\n * caller-supplied `formSlot`) plus an optional legend. Bring your own\n * container — drop it bare into a sidebar, wrap it in a `Card`, or stack it\n * above the grid.\n */\nexport function PlateMapForm<T extends WellRecord = WellRecord>({\n fields,\n value,\n onChange,\n selectionSize,\n onApply,\n onClear,\n applyLabel,\n clearLabel,\n extras,\n legend,\n formSlot,\n className,\n}: PlateMapFormProps<T>) {\n return (\n <div data-slot=\"plate-map-form\" className={cn(\"flex flex-col gap-3\", className)}>\n {formSlot ?? (\n <WellMetadataForm<T>\n fields={fields}\n value={value}\n onChange={onChange}\n selectionSize={selectionSize}\n onApply={onApply}\n onClear={onClear}\n applyLabel={applyLabel}\n clearLabel={clearLabel}\n extras={extras}\n />\n )}\n {legend ? (\n <>\n <Separator />\n {legend}\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["PlateMapForm","fields","value","onChange","selectionSize","onApply","onClear","applyLabel","clearLabel","extras","legend","formSlot","className","jsxs","cn","jsx","WellMetadataForm","Fragment","Separator"],"mappings":";;;;AAqCO,SAASA,EAAgD;AAAA,EAC9D,QAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,eAAAC;AAAA,EACA,SAAAC;AAAA,EACA,SAAAC;AAAA,EACA,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,WAAAC;AACF,GAAyB;AACvB,SACE,gBAAAC,EAAC,SAAI,aAAU,kBAAiB,WAAWC,EAAG,uBAAuBF,CAAS,GAC3E,UAAA;AAAA,IAAAD,KACC,gBAAAI;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,QAAAf;AAAA,QACA,OAAAC;AAAA,QACA,UAAAC;AAAA,QACA,eAAAC;AAAA,QACA,SAAAC;AAAA,QACA,SAAAC;AAAA,QACA,YAAAC;AAAA,QACA,YAAAC;AAAA,QACA,QAAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAGHC,IACC,gBAAAG,EAAAI,GAAA,EACE,UAAA;AAAA,MAAA,gBAAAF,EAACG,GAAA,EAAU;AAAA,MACVR;AAAA,IAAA,EAAA,CACH,IACE;AAAA,EAAA,GACN;AAEJ;"}
1
+ {"version":3,"file":"PlateMapForm.js","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapForm.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { WellMetadataForm } from \"./WellMetadataForm\";\n\nimport type { WellField, WellRecord } from \"./types\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateMapFormProps<T extends WellRecord = WellRecord> {\n fields: WellField<T>[];\n /** Staged values applied across the current selection on Apply. */\n value: Partial<T>;\n onChange: (next: Partial<T>) => void;\n selectionSize: number;\n onApply: () => void;\n onClear: () => void;\n applyLabel?: string;\n clearLabel?: string;\n /** Heading shown when nothing is selected. */\n selectionEmptyLabel?: string;\n /** Heading shown with a selection. */\n selectionCountLabel?: (selectionSize: number) => string;\n /** Helper slot rendered between the fields and the action row. */\n extras?: React.ReactNode;\n /** Legend block rendered beneath the form, separated by a divider. */\n legend?: React.ReactNode;\n /**\n * Fully replaces the built-in `WellMetadataForm` — e.g. a drag-and-drop\n * source palette. The legend slot still renders beneath the replacement.\n */\n formSlot?: React.ReactNode;\n className?: string;\n}\n\n/**\n * Card-agnostic metadata form panel. Renders the staged-value form (or a\n * caller-supplied `formSlot`) plus an optional legend. Bring your own\n * container — drop it bare into a sidebar, wrap it in a `Card`, or stack it\n * above the grid.\n */\nexport function PlateMapForm<T extends WellRecord = WellRecord>({\n fields,\n value,\n onChange,\n selectionSize,\n onApply,\n onClear,\n applyLabel,\n clearLabel,\n selectionEmptyLabel,\n selectionCountLabel,\n extras,\n legend,\n formSlot,\n className,\n}: PlateMapFormProps<T>) {\n return (\n <div data-slot=\"plate-map-form\" className={cn(\"flex flex-col gap-3\", className)}>\n {formSlot ?? (\n <WellMetadataForm<T>\n fields={fields}\n value={value}\n onChange={onChange}\n selectionSize={selectionSize}\n onApply={onApply}\n onClear={onClear}\n applyLabel={applyLabel}\n clearLabel={clearLabel}\n selectionEmptyLabel={selectionEmptyLabel}\n selectionCountLabel={selectionCountLabel}\n extras={extras}\n />\n )}\n {legend ? (\n <>\n <Separator />\n {legend}\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["PlateMapForm","fields","value","onChange","selectionSize","onApply","onClear","applyLabel","clearLabel","selectionEmptyLabel","selectionCountLabel","extras","legend","formSlot","className","jsxs","cn","jsx","WellMetadataForm","Fragment","Separator"],"mappings":";;;;AAyCO,SAASA,EAAgD;AAAA,EAC9D,QAAAC;AAAA,EACA,OAAAC;AAAA,EACA,UAAAC;AAAA,EACA,eAAAC;AAAA,EACA,SAAAC;AAAA,EACA,SAAAC;AAAA,EACA,YAAAC;AAAA,EACA,YAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,qBAAAC;AAAA,EACA,QAAAC;AAAA,EACA,QAAAC;AAAA,EACA,UAAAC;AAAA,EACA,WAAAC;AACF,GAAyB;AACvB,SACE,gBAAAC,EAAC,SAAI,aAAU,kBAAiB,WAAWC,EAAG,uBAAuBF,CAAS,GAC3E,UAAA;AAAA,IAAAD,KACC,gBAAAI;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,QAAAjB;AAAA,QACA,OAAAC;AAAA,QACA,UAAAC;AAAA,QACA,eAAAC;AAAA,QACA,SAAAC;AAAA,QACA,SAAAC;AAAA,QACA,YAAAC;AAAA,QACA,YAAAC;AAAA,QACA,qBAAAC;AAAA,QACA,qBAAAC;AAAA,QACA,QAAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAGHC,IACC,gBAAAG,EAAAI,GAAA,EACE,UAAA;AAAA,MAAA,gBAAAF,EAACG,GAAA,EAAU;AAAA,MACVR;AAAA,IAAA,EAAA,CACH,IACE;AAAA,EAAA,GACN;AAEJ;"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),X=require("react"),Y=require("./PlatePaintGrid.cjs"),h=require("./wellGrid.cjs"),Z=require("../../../ui/separator.cjs"),j=require("../../../lib/utils.cjs");function $(l){const s=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const t in l)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(l,t);Object.defineProperty(s,t,n.get?n:{enumerable:!0,get:()=>l[t]})}}return s.default=l,Object.freeze(s)}const ee=$(X);function te(l,s){return s.map(t=>{const n=l[t.key];if(n==null||n==="")return null;if(t.kind==="select")return(t.options??[]).find(o=>o.value===n)?.label??String(n);if(t.kind==="multiselect"){const i=Array.isArray(n)?n:[];return i.length===0?null:i.map(o=>(t.options??[]).find(c=>c.value===o)?.label??o).join(", ")}return t.kind==="boolean"?n?t.label:null:String(n)}).filter(Boolean)}function ne({format:l,rows:s,columns:t,values:n,selection:i,onSelectionChange:o,colorForWell:c,fields:m,renderHoverSummary:y,onHoveredWellChange:g,hoveredWellId:x,toolbar:f,hideSelectionControls:N=!1,selectAllLabel:P="Select all",deselectAllLabel:w="Deselect all",emptyWellFillColor:S,wellShape:k,framed:q,wrapWell:C,highlightedWellIds:O,onWellDoubleClick:A,selectionFillMode:G,flashWellId:H,flashWellKey:D,cellSize:M,autoScale:R,minCellSize:_,maxCellSize:F,groups:d,activeGroupId:T,onGroupClick:z,className:B}){const E=h.resolveDimensions(l,s,t),[I,J]=ee.useState(null),p=x!==void 0,a=p?x:I,K=()=>o(new Set(h.allPositions(E))),L=()=>o(new Set),u=a?n.get(a):void 0,Q=u&&m?te(u,m):[],U=a?y?.(u,a)??[a,...Q].join(" • "):" ",v=!N,V=!!f||v;return e.jsxs("div",{"data-slot":"plate-map-grid",className:j.cn("flex flex-col gap-1.5",B),children:[V?e.jsxs("div",{className:"flex flex-wrap items-center justify-start gap-3",children:[f,v?e.jsxs("div",{className:"flex items-center gap-2 text-xs",children:[e.jsx("button",{type:"button",className:"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline",onClick:K,children:P}),e.jsx("span",{className:"text-muted-foreground/60",children:"·"}),e.jsx("button",{type:"button",className:"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline",onClick:L,children:w})]}):null]}):null,e.jsx("div",{className:"h-5 text-xs text-muted-foreground",children:U}),e.jsx(Y.PlatePaintGrid,{format:l,rows:s,columns:t,values:n,selection:i,onSelectionChange:o,colorForWell:c,emptyWellFillColor:S,wellShape:k,framed:q,wrapWell:C,highlightedWellIds:O,onWellHover:r=>{p||J(r),g?.(r)},onWellDoubleClick:A,selectionFillMode:G,flashWellId:H,flashWellKey:D,cellSize:M,autoScale:R,minCellSize:_,maxCellSize:F}),d&&d.length>0?e.jsxs(e.Fragment,{children:[e.jsx(Z.Separator,{className:"mt-2"}),e.jsx("div",{className:"flex max-h-28 flex-wrap gap-3 overflow-y-auto pt-1",children:d.map(r=>{const W=r.id===T,b=r.count??r.wellIds?.length;return e.jsxs("button",{type:"button",disabled:r.disabled,onClick:()=>z?.(r),className:j.cn("flex w-16 flex-col items-center gap-1 rounded-md px-1 py-1 text-center text-xs transition-colors","text-muted-foreground hover:bg-accent/60",W&&"bg-muted text-foreground ring-1 ring-primary/40",r.disabled&&"pointer-events-none opacity-50"),title:r.label,children:[e.jsx("span",{className:"size-7 rounded-full border",style:{backgroundColor:r.color,borderColor:r.borderColor},"aria-hidden":!0}),e.jsx("span",{className:"w-full truncate text-foreground",children:r.label}),b===void 0?null:e.jsxs("span",{className:"text-[0.7rem] leading-none text-muted-foreground",children:[b," wells"]})]},r.id)})})]}):null]})}exports.PlateMapGrid=ne;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),$=require("react"),ee=require("./PlatePaintGrid.cjs"),h=require("./wellGrid.cjs"),te=require("../../../ui/separator.cjs"),j=require("../../../lib/utils.cjs");function ne(l){const s=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(l){for(const t in l)if(t!=="default"){const n=Object.getOwnPropertyDescriptor(l,t);Object.defineProperty(s,t,n.get?n:{enumerable:!0,get:()=>l[t]})}}return s.default=l,Object.freeze(s)}const re=ne($);function le(l,s){return s.map(t=>{const n=l[t.key];if(n==null||n==="")return null;if(t.kind==="select")return(t.options??[]).find(o=>o.value===n)?.label??String(n);if(t.kind==="multiselect"){const i=Array.isArray(n)?n:[];return i.length===0?null:i.map(o=>(t.options??[]).find(c=>c.value===o)?.label??o).join(", ")}return t.kind==="boolean"?n?t.label:null:String(n)}).filter(Boolean)}function se({format:l,rows:s,columns:t,values:n,selection:i,onSelectionChange:o,colorForWell:c,fields:m,renderHoverSummary:y,onHoveredWellChange:g,hoveredWellId:x,banner:w,toolbar:f,footer:N,hideSelectionControls:P=!1,selectAllLabel:S="Select all",deselectAllLabel:k="Deselect all",emptyWellFillColor:q,wellShape:C,framed:O,wrapWell:A,quickPaint:G,highlightedWellIds:H,onWellDoubleClick:D,selectionFillMode:M,flashWellId:R,flashWellKey:_,cellSize:F,autoScale:T,minCellSize:z,maxCellSize:B,groups:d,activeGroupId:E,onGroupClick:I,className:J}){const K=h.resolveDimensions(l,s,t),[L,Q]=re.useState(null),p=x!==void 0,a=p?x:L,U=()=>o(new Set(h.allPositions(K))),V=()=>o(new Set),u=a?n.get(a):void 0,W=u&&m?le(u,m):[],X=a?y?.(u,a)??[a,...W].join(" • "):" ",v=!P,Y=!!f||v;return e.jsxs("div",{"data-slot":"plate-map-grid",className:j.cn("flex w-full min-w-0 flex-col gap-1.5",J),children:[w,Y?e.jsxs("div",{className:"flex flex-wrap items-center justify-start gap-3",children:[f,v?e.jsxs("div",{className:"flex items-center gap-2 text-xs",children:[e.jsx("button",{type:"button",className:"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline",onClick:U,children:S}),e.jsx("span",{className:"text-muted-foreground/60",children:"·"}),e.jsx("button",{type:"button",className:"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline",onClick:V,children:k})]}):null]}):null,e.jsx("div",{className:"h-5 truncate text-xs text-muted-foreground",children:X}),e.jsx(ee.PlatePaintGrid,{format:l,rows:s,columns:t,values:n,selection:i,onSelectionChange:o,colorForWell:c,emptyWellFillColor:q,wellShape:C,framed:O,wrapWell:A,quickPaint:G,highlightedWellIds:H,onWellHover:r=>{p||Q(r),g?.(r)},onWellDoubleClick:D,selectionFillMode:M,flashWellId:R,flashWellKey:_,cellSize:F,autoScale:T,minCellSize:z,maxCellSize:B}),d&&d.length>0?e.jsxs(e.Fragment,{children:[e.jsx(te.Separator,{className:"mt-2"}),e.jsx("div",{className:"flex max-h-28 flex-wrap gap-3 overflow-y-auto pt-1",children:d.map(r=>{const Z=r.id===E,b=r.count??r.wellIds?.length;return e.jsxs("button",{type:"button",disabled:r.disabled,onClick:()=>I?.(r),className:j.cn("flex w-16 flex-col items-center gap-1 rounded-md px-1 py-1 text-center text-xs transition-colors","text-muted-foreground hover:bg-accent/60",Z&&"bg-muted text-foreground ring-1 ring-primary/40",r.disabled&&"pointer-events-none opacity-50"),title:r.label,children:[e.jsx("span",{className:"size-7 rounded-full border",style:{backgroundColor:r.color,borderColor:r.borderColor},"aria-hidden":!0}),e.jsx("span",{className:"w-full truncate text-foreground",children:r.label}),b===void 0?null:e.jsxs("span",{className:"text-[0.7rem] leading-none text-muted-foreground",children:[b," wells"]})]},r.id)})})]}):null,N]})}exports.PlateMapGrid=se;
2
2
  //# sourceMappingURL=PlateMapGrid.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"PlateMapGrid.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapGrid.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { PlatePaintGrid } from \"./PlatePaintGrid\";\nimport { allPositions, resolveDimensions } from \"./wellGrid\";\n\nimport type { WellShape } from \"./PlatePaintGrid\";\nimport type { PlateFormat, PlateMapGroupOption, WellField, WellId, WellRecord } from \"./types\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nfunction formatHoverFields<T extends WellRecord>(entry: T, fields: WellField<T>[]): string[] {\n return fields\n .map((f) => {\n const v = entry[f.key];\n if (v === undefined || v === null || v === \"\") return null;\n if (f.kind === \"select\") {\n const opt = (f.options ?? []).find((o) => o.value === v);\n return opt?.label ?? String(v);\n }\n if (f.kind === \"multiselect\") {\n const arr = Array.isArray(v) ? (v as string[]) : [];\n if (arr.length === 0) return null;\n return arr.map((item) => (f.options ?? []).find((o) => o.value === item)?.label ?? item).join(\", \");\n }\n if (f.kind === \"boolean\") {\n return v ? f.label : null;\n }\n return String(v);\n })\n .filter(Boolean) as string[];\n}\n\nexport interface PlateMapGridProps<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 /** Resolves the SVG fill color for a well. */\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n /** Field schema used to build the default hover summary string. */\n fields?: WellField<T>[];\n /** Custom hover summary for the strip above the grid. */\n renderHoverSummary?: (well: T | undefined, wellId: WellId) => React.ReactNode;\n /** Fires whenever the currently hovered well changes (null on leave). */\n onHoveredWellChange?: (wellId: WellId | null) => void;\n /**\n * Controlled hovered well id. When provided, the panel reflects this value\n * instead of tracking hover internally — useful for syncing the hover\n * summary with external state (e.g. resetting it on a plate switch).\n */\n hoveredWellId?: WellId | null;\n /** Controls rendered to the left of the built-in select/deselect links. */\n toolbar?: React.ReactNode;\n /** Hide the built-in \"Select all\" / \"Deselect all\" links. */\n hideSelectionControls?: boolean;\n selectAllLabel?: string;\n deselectAllLabel?: string;\n /** Fill color for empty wells. Pass `null` to delegate empty wells to `colorForWell`. */\n emptyWellFillColor?: string | null;\n /** Well shape forwarded to `PlatePaintGrid`. Defaults to `\"rect\"`. */\n wellShape?: WellShape;\n /** When true, wraps the grid in a card-like plate frame (rounded + border + soft shadow). */\n framed?: boolean;\n /** Render-prop that places a node inside each absolute-positioned well cell. */\n wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;\n /** Wells to highlight (e.g. when hovering a legend item externally). */\n highlightedWellIds?: ReadonlySet<WellId>;\n onWellDoubleClick?: (wellId: WellId) => void;\n selectionFillMode?: \"selection\" | \"well\";\n flashWellId?: WellId;\n flashWellKey?: number;\n /** Fixed well size. When unset, the grid grows with the available width. */\n cellSize?: number;\n autoScale?: boolean;\n minCellSize?: number;\n maxCellSize?: number;\n /** Optional grouped well shortcuts rendered under the grid. */\n groups?: PlateMapGroupOption[];\n activeGroupId?: string;\n onGroupClick?: (group: PlateMapGroupOption) => void;\n className?: string;\n}\n\n/**\n * Card-agnostic plate grid panel. Renders the selection toolbar, hover\n * summary, the interactive `PlatePaintGrid`, and optional group shortcuts —\n * and owns the ephemeral hover state internally. The plate title, plate\n * selector, and actions menu are intentionally NOT part of this panel; compose\n * `PlateMapPlateSelector` and `PlateMapActionsMenu` around it for full layout\n * freedom.\n */\nexport function PlateMapGrid<T extends WellRecord = WellRecord>({\n format,\n rows,\n columns,\n values,\n selection,\n onSelectionChange,\n colorForWell,\n fields,\n renderHoverSummary,\n onHoveredWellChange,\n hoveredWellId,\n toolbar,\n hideSelectionControls = false,\n selectAllLabel = \"Select all\",\n deselectAllLabel = \"Deselect all\",\n emptyWellFillColor,\n wellShape,\n framed,\n wrapWell,\n highlightedWellIds,\n onWellDoubleClick,\n selectionFillMode,\n flashWellId,\n flashWellKey,\n cellSize,\n autoScale,\n minCellSize,\n maxCellSize,\n groups,\n activeGroupId,\n onGroupClick,\n className,\n}: PlateMapGridProps<T>) {\n const dims = resolveDimensions(format, rows, columns);\n const [internalHoverPos, setInternalHoverPos] = React.useState<WellId | null>(null);\n const isHoverControlled = hoveredWellId !== undefined;\n const hoverPos = isHoverControlled ? hoveredWellId : internalHoverPos;\n\n const selectAll = () => onSelectionChange(new Set(allPositions(dims)));\n const deselectAll = () => onSelectionChange(new Set());\n\n const hoverEntry = hoverPos ? values.get(hoverPos) : undefined;\n const hoverFields = hoverEntry && fields ? formatHoverFields(hoverEntry, fields) : [];\n const hoverSummary = hoverPos\n ? (renderHoverSummary?.(hoverEntry, hoverPos) ?? [hoverPos, ...hoverFields].join(\" • \"))\n : \" \";\n\n const showSelectionControls = !hideSelectionControls;\n const showToolbarRow = !!toolbar || showSelectionControls;\n\n return (\n <div data-slot=\"plate-map-grid\" className={cn(\"flex flex-col gap-1.5\", className)}>\n {showToolbarRow ? (\n <div className=\"flex flex-wrap items-center justify-start gap-3\">\n {toolbar}\n {showSelectionControls ? (\n <div className=\"flex items-center gap-2 text-xs\">\n <button\n type=\"button\"\n className=\"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline\"\n onClick={selectAll}\n >\n {selectAllLabel}\n </button>\n <span className=\"text-muted-foreground/60\">·</span>\n <button\n type=\"button\"\n className=\"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline\"\n onClick={deselectAll}\n >\n {deselectAllLabel}\n </button>\n </div>\n ) : null}\n </div>\n ) : null}\n <div className=\"h-5 text-xs text-muted-foreground\">{hoverSummary}</div>\n <PlatePaintGrid\n format={format}\n rows={rows}\n columns={columns}\n values={values}\n selection={selection}\n onSelectionChange={onSelectionChange}\n colorForWell={colorForWell}\n emptyWellFillColor={emptyWellFillColor}\n wellShape={wellShape}\n framed={framed}\n wrapWell={wrapWell}\n highlightedWellIds={highlightedWellIds}\n onWellHover={(wellId) => {\n if (!isHoverControlled) setInternalHoverPos(wellId);\n onHoveredWellChange?.(wellId);\n }}\n onWellDoubleClick={onWellDoubleClick}\n selectionFillMode={selectionFillMode}\n flashWellId={flashWellId}\n flashWellKey={flashWellKey}\n cellSize={cellSize}\n autoScale={autoScale}\n minCellSize={minCellSize}\n maxCellSize={maxCellSize}\n />\n {groups && groups.length > 0 ? (\n <>\n <Separator className=\"mt-2\" />\n <div className=\"flex max-h-28 flex-wrap gap-3 overflow-y-auto pt-1\">\n {groups.map((group) => {\n const isActive = group.id === activeGroupId;\n const count = group.count ?? group.wellIds?.length;\n return (\n <button\n key={group.id}\n type=\"button\"\n disabled={group.disabled}\n onClick={() => onGroupClick?.(group)}\n className={cn(\n \"flex w-16 flex-col items-center gap-1 rounded-md px-1 py-1 text-center text-xs transition-colors\",\n \"text-muted-foreground hover:bg-accent/60\",\n isActive && \"bg-muted text-foreground ring-1 ring-primary/40\",\n group.disabled && \"pointer-events-none opacity-50\",\n )}\n title={group.label}\n >\n <span\n className=\"size-7 rounded-full border\"\n style={{\n backgroundColor: group.color,\n borderColor: group.borderColor,\n }}\n aria-hidden\n />\n <span className=\"w-full truncate text-foreground\">{group.label}</span>\n {count === undefined ? null : (\n <span className=\"text-[0.7rem] leading-none text-muted-foreground\">{count} wells</span>\n )}\n </button>\n );\n })}\n </div>\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["formatHoverFields","entry","fields","f","v","arr","item","o","PlateMapGrid","format","rows","columns","values","selection","onSelectionChange","colorForWell","renderHoverSummary","onHoveredWellChange","hoveredWellId","toolbar","hideSelectionControls","selectAllLabel","deselectAllLabel","emptyWellFillColor","wellShape","framed","wrapWell","highlightedWellIds","onWellDoubleClick","selectionFillMode","flashWellId","flashWellKey","cellSize","autoScale","minCellSize","maxCellSize","groups","activeGroupId","onGroupClick","className","dims","resolveDimensions","internalHoverPos","setInternalHoverPos","React","isHoverControlled","hoverPos","selectAll","allPositions","deselectAll","hoverEntry","hoverFields","hoverSummary","showSelectionControls","showToolbarRow","jsxs","cn","jsx","PlatePaintGrid","wellId","Fragment","Separator","group","isActive","count"],"mappings":"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"}
1
+ {"version":3,"file":"PlateMapGrid.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateMapGrid.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { PlatePaintGrid } from \"./PlatePaintGrid\";\nimport { allPositions, resolveDimensions } from \"./wellGrid\";\n\nimport type { WellShape } from \"./PlatePaintGrid\";\nimport type { PlateFormat, PlateMapGroupOption, WellField, WellId, WellRecord } from \"./types\";\n\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nfunction formatHoverFields<T extends WellRecord>(entry: T, fields: WellField<T>[]): string[] {\n return fields\n .map((f) => {\n const v = entry[f.key];\n if (v === undefined || v === null || v === \"\") return null;\n if (f.kind === \"select\") {\n const opt = (f.options ?? []).find((o) => o.value === v);\n return opt?.label ?? String(v);\n }\n if (f.kind === \"multiselect\") {\n const arr = Array.isArray(v) ? (v as string[]) : [];\n if (arr.length === 0) return null;\n return arr.map((item) => (f.options ?? []).find((o) => o.value === item)?.label ?? item).join(\", \");\n }\n if (f.kind === \"boolean\") {\n return v ? f.label : null;\n }\n return String(v);\n })\n .filter(Boolean) as string[];\n}\n\nexport interface PlateMapGridProps<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 /** Resolves the SVG fill color for a well. */\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n /** Field schema used to build the default hover summary string. */\n fields?: WellField<T>[];\n /** Custom hover summary for the strip above the grid. */\n renderHoverSummary?: (well: T | undefined, wellId: WellId) => React.ReactNode;\n /** Fires whenever the currently hovered well changes (null on leave). */\n onHoveredWellChange?: (wellId: WellId | null) => void;\n /**\n * Controlled hovered well id. When provided, the panel reflects this value\n * instead of tracking hover internally — useful for syncing the hover\n * summary with external state (e.g. resetting it on a plate switch).\n */\n hoveredWellId?: WellId | null;\n /** Slot rendered above the toolbar row, inside the grid panel. */\n banner?: React.ReactNode;\n /** Controls rendered to the left of the built-in select/deselect links. */\n toolbar?: React.ReactNode;\n /** Slot rendered below the grid (and below any group shortcuts). */\n footer?: React.ReactNode;\n /** Hide the built-in \"Select all\" / \"Deselect all\" links. */\n hideSelectionControls?: boolean;\n selectAllLabel?: string;\n deselectAllLabel?: string;\n /** Fill color for empty wells. Pass `null` to delegate empty wells to `colorForWell`. */\n emptyWellFillColor?: string | null;\n /** Well shape forwarded to `PlatePaintGrid`. Defaults to `\"rect\"`. */\n wellShape?: WellShape;\n /** When true, wraps the grid in a card-like plate frame (rounded + border + soft shadow). */\n framed?: boolean;\n /** Render-prop that places a node inside each absolute-positioned well cell. */\n wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;\n /** Swatch strip anchored above the selection for one-click painting. */\n quickPaint?: React.ReactNode;\n /** Wells to highlight (e.g. when hovering a legend item externally). */\n highlightedWellIds?: ReadonlySet<WellId>;\n onWellDoubleClick?: (wellId: WellId) => void;\n /**\n * How a selected well is filled. `\"well\"` (default) keeps the well's own\n * colour and shows selection via the ring, so an applied edit is visible\n * immediately. `\"selection\"` replaces the fill with the selection tint.\n */\n selectionFillMode?: \"selection\" | \"well\";\n flashWellId?: WellId;\n flashWellKey?: number;\n /** Fixed well size. When unset, the grid grows with the available width. */\n cellSize?: number;\n autoScale?: boolean;\n minCellSize?: number;\n maxCellSize?: number;\n /** Optional grouped well shortcuts rendered under the grid. */\n groups?: PlateMapGroupOption[];\n activeGroupId?: string;\n onGroupClick?: (group: PlateMapGroupOption) => void;\n className?: string;\n}\n\n/**\n * Card-agnostic plate grid panel. Renders the selection toolbar, hover\n * summary, the interactive `PlatePaintGrid`, and optional group shortcuts —\n * and owns the ephemeral hover state internally. The plate title, plate\n * selector, and actions menu are intentionally NOT part of this panel; compose\n * `PlateMapPlateSelector` and `PlateMapActionsMenu` around it for full layout\n * freedom.\n */\nexport function PlateMapGrid<T extends WellRecord = WellRecord>({\n format,\n rows,\n columns,\n values,\n selection,\n onSelectionChange,\n colorForWell,\n fields,\n renderHoverSummary,\n onHoveredWellChange,\n hoveredWellId,\n banner,\n toolbar,\n footer,\n hideSelectionControls = false,\n selectAllLabel = \"Select all\",\n deselectAllLabel = \"Deselect all\",\n emptyWellFillColor,\n wellShape,\n framed,\n wrapWell,\n quickPaint,\n highlightedWellIds,\n onWellDoubleClick,\n selectionFillMode,\n flashWellId,\n flashWellKey,\n cellSize,\n autoScale,\n minCellSize,\n maxCellSize,\n groups,\n activeGroupId,\n onGroupClick,\n className,\n}: PlateMapGridProps<T>) {\n const dims = resolveDimensions(format, rows, columns);\n const [internalHoverPos, setInternalHoverPos] = React.useState<WellId | null>(null);\n const isHoverControlled = hoveredWellId !== undefined;\n const hoverPos = isHoverControlled ? hoveredWellId : internalHoverPos;\n\n const selectAll = () => onSelectionChange(new Set(allPositions(dims)));\n const deselectAll = () => onSelectionChange(new Set());\n\n const hoverEntry = hoverPos ? values.get(hoverPos) : undefined;\n const hoverFields = hoverEntry && fields ? formatHoverFields(hoverEntry, fields) : [];\n const hoverSummary = hoverPos\n ? (renderHoverSummary?.(hoverEntry, hoverPos) ?? [hoverPos, ...hoverFields].join(\" • \"))\n : \" \";\n\n const showSelectionControls = !hideSelectionControls;\n const showToolbarRow = !!toolbar || showSelectionControls;\n\n return (\n <div data-slot=\"plate-map-grid\" className={cn(\"flex w-full min-w-0 flex-col gap-1.5\", className)}>\n {banner}\n {showToolbarRow ? (\n <div className=\"flex flex-wrap items-center justify-start gap-3\">\n {toolbar}\n {showSelectionControls ? (\n <div className=\"flex items-center gap-2 text-xs\">\n <button\n type=\"button\"\n className=\"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline\"\n onClick={selectAll}\n >\n {selectAllLabel}\n </button>\n <span className=\"text-muted-foreground/60\">·</span>\n <button\n type=\"button\"\n className=\"font-medium text-primary underline-offset-2 hover:text-primary/80 hover:underline\"\n onClick={deselectAll}\n >\n {deselectAllLabel}\n </button>\n </div>\n ) : null}\n </div>\n ) : null}\n <div className=\"h-5 truncate text-xs text-muted-foreground\">{hoverSummary}</div>\n <PlatePaintGrid\n format={format}\n rows={rows}\n columns={columns}\n values={values}\n selection={selection}\n onSelectionChange={onSelectionChange}\n colorForWell={colorForWell}\n emptyWellFillColor={emptyWellFillColor}\n wellShape={wellShape}\n framed={framed}\n wrapWell={wrapWell}\n quickPaint={quickPaint}\n highlightedWellIds={highlightedWellIds}\n onWellHover={(wellId) => {\n if (!isHoverControlled) setInternalHoverPos(wellId);\n onHoveredWellChange?.(wellId);\n }}\n onWellDoubleClick={onWellDoubleClick}\n selectionFillMode={selectionFillMode}\n flashWellId={flashWellId}\n flashWellKey={flashWellKey}\n cellSize={cellSize}\n autoScale={autoScale}\n minCellSize={minCellSize}\n maxCellSize={maxCellSize}\n />\n {groups && groups.length > 0 ? (\n <>\n <Separator className=\"mt-2\" />\n <div className=\"flex max-h-28 flex-wrap gap-3 overflow-y-auto pt-1\">\n {groups.map((group) => {\n const isActive = group.id === activeGroupId;\n const count = group.count ?? group.wellIds?.length;\n return (\n <button\n key={group.id}\n type=\"button\"\n disabled={group.disabled}\n onClick={() => onGroupClick?.(group)}\n className={cn(\n \"flex w-16 flex-col items-center gap-1 rounded-md px-1 py-1 text-center text-xs transition-colors\",\n \"text-muted-foreground hover:bg-accent/60\",\n isActive && \"bg-muted text-foreground ring-1 ring-primary/40\",\n group.disabled && \"pointer-events-none opacity-50\",\n )}\n title={group.label}\n >\n <span\n className=\"size-7 rounded-full border\"\n style={{\n backgroundColor: group.color,\n borderColor: group.borderColor,\n }}\n aria-hidden\n />\n <span className=\"w-full truncate text-foreground\">{group.label}</span>\n {count === undefined ? null : (\n <span className=\"text-[0.7rem] leading-none text-muted-foreground\">{count} wells</span>\n )}\n </button>\n );\n })}\n </div>\n </>\n ) : null}\n {footer}\n </div>\n );\n}\n"],"names":["formatHoverFields","entry","fields","f","v","arr","item","o","PlateMapGrid","format","rows","columns","values","selection","onSelectionChange","colorForWell","renderHoverSummary","onHoveredWellChange","hoveredWellId","banner","toolbar","footer","hideSelectionControls","selectAllLabel","deselectAllLabel","emptyWellFillColor","wellShape","framed","wrapWell","quickPaint","highlightedWellIds","onWellDoubleClick","selectionFillMode","flashWellId","flashWellKey","cellSize","autoScale","minCellSize","maxCellSize","groups","activeGroupId","onGroupClick","className","dims","resolveDimensions","internalHoverPos","setInternalHoverPos","React","isHoverControlled","hoverPos","selectAll","allPositions","deselectAll","hoverEntry","hoverFields","hoverSummary","showSelectionControls","showToolbarRow","jsxs","cn","jsx","PlatePaintGrid","wellId","Fragment","Separator","group","isActive","count"],"mappings":"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"}
@@ -22,8 +22,12 @@ export interface PlateMapGridProps<T extends WellRecord = WellRecord> {
22
22
  * summary with external state (e.g. resetting it on a plate switch).
23
23
  */
24
24
  hoveredWellId?: WellId | null;
25
+ /** Slot rendered above the toolbar row, inside the grid panel. */
26
+ banner?: React.ReactNode;
25
27
  /** Controls rendered to the left of the built-in select/deselect links. */
26
28
  toolbar?: React.ReactNode;
29
+ /** Slot rendered below the grid (and below any group shortcuts). */
30
+ footer?: React.ReactNode;
27
31
  /** Hide the built-in "Select all" / "Deselect all" links. */
28
32
  hideSelectionControls?: boolean;
29
33
  selectAllLabel?: string;
@@ -36,9 +40,16 @@ export interface PlateMapGridProps<T extends WellRecord = WellRecord> {
36
40
  framed?: boolean;
37
41
  /** Render-prop that places a node inside each absolute-positioned well cell. */
38
42
  wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;
43
+ /** Swatch strip anchored above the selection for one-click painting. */
44
+ quickPaint?: React.ReactNode;
39
45
  /** Wells to highlight (e.g. when hovering a legend item externally). */
40
46
  highlightedWellIds?: ReadonlySet<WellId>;
41
47
  onWellDoubleClick?: (wellId: WellId) => void;
48
+ /**
49
+ * How a selected well is filled. `"well"` (default) keeps the well's own
50
+ * colour and shows selection via the ring, so an applied edit is visible
51
+ * immediately. `"selection"` replaces the fill with the selection tint.
52
+ */
42
53
  selectionFillMode?: "selection" | "well";
43
54
  flashWellId?: WellId;
44
55
  flashWellKey?: number;
@@ -61,4 +72,4 @@ export interface PlateMapGridProps<T extends WellRecord = WellRecord> {
61
72
  * `PlateMapPlateSelector` and `PlateMapActionsMenu` around it for full layout
62
73
  * freedom.
63
74
  */
64
- export declare function PlateMapGrid<T extends WellRecord = WellRecord>({ format, rows, columns, values, selection, onSelectionChange, colorForWell, fields, renderHoverSummary, onHoveredWellChange, hoveredWellId, toolbar, hideSelectionControls, selectAllLabel, deselectAllLabel, emptyWellFillColor, wellShape, framed, wrapWell, highlightedWellIds, onWellDoubleClick, selectionFillMode, flashWellId, flashWellKey, cellSize, autoScale, minCellSize, maxCellSize, groups, activeGroupId, onGroupClick, className, }: PlateMapGridProps<T>): import("react/jsx-runtime").JSX.Element;
75
+ export declare function PlateMapGrid<T extends WellRecord = WellRecord>({ format, rows, columns, values, selection, onSelectionChange, colorForWell, fields, renderHoverSummary, onHoveredWellChange, hoveredWellId, banner, toolbar, footer, hideSelectionControls, selectAllLabel, deselectAllLabel, emptyWellFillColor, wellShape, framed, wrapWell, quickPaint, highlightedWellIds, onWellDoubleClick, selectionFillMode, flashWellId, flashWellKey, cellSize, autoScale, minCellSize, maxCellSize, groups, activeGroupId, onGroupClick, className, }: PlateMapGridProps<T>): import("react/jsx-runtime").JSX.Element;