@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.157.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 (68) hide show
  1. package/README.md +113 -0
  2. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +1 -1
  3. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -1
  4. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.d.ts +5 -1
  5. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +32 -30
  6. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -1
  7. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +1 -1
  8. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -1
  9. package/dist/components/composed/PlateMapEditor/PlateMapEditor.d.ts +269 -13
  10. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +392 -310
  11. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -1
  12. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs +1 -1
  13. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs.map +1 -1
  14. package/dist/components/composed/PlateMapEditor/PlateMapForm.d.ts +5 -1
  15. package/dist/components/composed/PlateMapEditor/PlateMapForm.js +19 -15
  16. package/dist/components/composed/PlateMapEditor/PlateMapForm.js.map +1 -1
  17. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs +1 -1
  18. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs.map +1 -1
  19. package/dist/components/composed/PlateMapEditor/PlateMapGrid.d.ts +12 -1
  20. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js +69 -63
  21. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js.map +1 -1
  22. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs +1 -1
  23. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs.map +1 -1
  24. package/dist/components/composed/PlateMapEditor/PlateMapManifest.d.ts +13 -4
  25. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js +38 -29
  26. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js.map +1 -1
  27. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +1 -1
  28. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -1
  29. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.d.ts +6 -1
  30. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +490 -275
  31. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -1
  32. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +1 -1
  33. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -1
  34. package/dist/components/composed/PlateMapEditor/WellLegend.d.ts +8 -2
  35. package/dist/components/composed/PlateMapEditor/WellLegend.js +8 -7
  36. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -1
  37. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +1 -1
  38. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -1
  39. package/dist/components/composed/PlateMapEditor/WellManifestTable.d.ts +6 -2
  40. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +288 -256
  41. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -1
  42. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +1 -1
  43. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -1
  44. package/dist/components/composed/PlateMapEditor/WellMetadataForm.d.ts +5 -1
  45. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +61 -66
  46. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -1
  47. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs +2 -0
  48. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs.map +1 -0
  49. package/dist/components/composed/PlateMapEditor/WellQuickPaint.d.ts +23 -0
  50. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js +75 -0
  51. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js.map +1 -0
  52. package/dist/components/composed/PlateMapEditor/index.d.ts +6 -2
  53. package/dist/components/composed/PlateMapEditor/types.d.ts +65 -0
  54. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs +2 -0
  55. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs.map +1 -0
  56. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.d.ts +109 -0
  57. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js +246 -0
  58. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js.map +1 -0
  59. package/dist/composed/PlateMapEditor.cjs +1 -1
  60. package/dist/composed/PlateMapEditor.js +48 -42
  61. package/dist/composed/PlateMapEditor.js.map +1 -1
  62. package/dist/index.cjs +1 -1
  63. package/dist/index.css +1 -1
  64. package/dist/index.js +701 -695
  65. package/dist/index.js.map +1 -1
  66. package/dist/index.scoped.css +1 -1
  67. package/dist/index.tailwind.css +1 -1
  68. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"PlateMapEditor.cjs","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.cjs","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":"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"}
@@ -1,10 +1,90 @@
1
+ import { getPlateMapScopedWellId, PlateMapApplyScope } from './usePlateMapEditorState';
1
2
  import { PlateMapActionsMenuProps } from './PlateMapActionsMenu';
2
3
  import { PlateMapPlateSelectorVariant } from './PlateMapPlateSelector';
3
4
  import { WellShape } from './PlatePaintGrid';
4
- import { PlateFormat, PlateMapGroupOption, PlateMapPlateOption, WellColumn, WellField, WellId, WellRecord } from './types';
5
+ import { PlateFormat, PlateMapEditorLabels, PlateMapGroupOption, PlateMapPlateOption, WellColumn, WellField, WellId, WellRecord } from './types';
6
+ import { FilterColumnConfig } from '../../ui/data-table/data-table';
5
7
  import { Badge } from '../../ui/badge';
6
8
  import * as React from "react";
7
- export declare function getPlateMapScopedWellId(plateBarcode: string, wellId: WellId): WellId;
9
+ /**
10
+ * Width at which the form and plate stop stacking and sit side by side.
11
+ *
12
+ * These are **container** widths, not viewport widths — the editor measures the
13
+ * space it is actually given, so it lays out correctly inside a narrow panel,
14
+ * split pane, or drawer on a wide monitor, not just on a small device. Below
15
+ * the chosen width both regions are full-width and stacked.
16
+ *
17
+ * `"sm"` 640px · `"md"` 768px (default) · `"lg"` 1024px · `"xl"` 1280px ·
18
+ * `"never"` always stacked.
19
+ */
20
+ export type PlateMapEditorStackAt = "sm" | "md" | "lg" | "xl" | "never";
21
+ /** Where the metadata form sits relative to the plate grid. */
22
+ export type PlateMapEditorFormPlacement = "start" | "end" | "top" | "bottom";
23
+ export { getPlateMapScopedWellId };
24
+ /**
25
+ * Imperative handle for driving the editor's staged-edit state from outside —
26
+ * the supported way to render the metadata form somewhere the editor can't
27
+ * reach (a host sidebar, a drawer, a toolbar) while keeping the editor's
28
+ * selection and apply behaviour.
29
+ *
30
+ * Pair with `hideForm` and your own form:
31
+ *
32
+ * ```tsx
33
+ * const editor = React.useRef<PlateMapEditorHandle<MyWell>>(null);
34
+ * <PlateMapEditor ref={editor} hideForm … />
35
+ * <MySidebarForm
36
+ * onChange={(next) => editor.current?.setStaged(next)}
37
+ * onApply={() => editor.current?.apply()}
38
+ * onClear={() => editor.current?.clear()}
39
+ * />
40
+ * ```
41
+ */
42
+ /**
43
+ * Per-region class hooks. Each card also carries
44
+ * `data-plate-map-region="form|plate|manifest"`, so plain CSS can target the
45
+ * same regions without threading props.
46
+ */
47
+ export interface PlateMapEditorClassNames {
48
+ /** The row (or column) holding the form and plate cards. */
49
+ layout?: string;
50
+ /** The form column `Card`. Merged after the width rules, so width utilities here win. */
51
+ formCard?: string;
52
+ /** The plate column `Card`. */
53
+ plateCard?: string;
54
+ /** The manifest `Card`. */
55
+ manifestCard?: string;
56
+ /** The `PlateMapForm` panel inside the form card. */
57
+ form?: string;
58
+ /** The `PlateMapGrid` panel inside the plate card. */
59
+ grid?: string;
60
+ /** The `PlateMapManifest` panel inside the manifest card. */
61
+ manifest?: string;
62
+ }
63
+ /** Manifest table behaviour. Structural bits (`hideManifest`, `manifestTitle`, `manifestSlot`) stay top-level. */
64
+ export interface PlateMapEditorManifestOptions {
65
+ /** Enables the filter popover. */
66
+ filterable?: boolean;
67
+ /** Overrides which columns are filterable and how. */
68
+ filterColumns?: FilterColumnConfig[];
69
+ /** Enables the group-by selector. */
70
+ groupable?: boolean;
71
+ /** Field grouped by on first render. Requires `groupable`. */
72
+ defaultGroupBy?: string;
73
+ pageSize?: number;
74
+ pageSizeOptions?: number[];
75
+ /** Adds the copy-first-value-downward column action. Defaults to true. */
76
+ enableFillDown?: boolean;
77
+ }
78
+ export interface PlateMapEditorHandle<T extends WellRecord = WellRecord> {
79
+ /** Applies the staged record across the current selection. */
80
+ apply: (scope?: PlateMapApplyScope) => void;
81
+ /** Clears the current selection's wells. */
82
+ clear: (scope?: PlateMapApplyScope) => void;
83
+ /** Replaces the staged record. */
84
+ setStaged: (next: Partial<T>) => void;
85
+ /** Reads the staged record. */
86
+ getStaged: () => Partial<T>;
87
+ }
8
88
  export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends Omit<PlateMapActionsMenuProps, "hasEntries" | "className"> {
9
89
  format: PlateFormat;
10
90
  rows?: number;
@@ -15,10 +95,14 @@ export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends
15
95
  onSelectionChange: (next: Set<WellId>) => void;
16
96
  fields: WellField<T>[];
17
97
  tableColumns: WellColumn<T>[];
18
- /** Resolves the SVG fill color for a well. */
19
- colorForWell: (well: T | undefined, wellId: WellId) => string;
20
- /** Builds an empty record when a well is freshly created. */
21
- emptyEntry: (wellId: WellId) => T;
98
+ /**
99
+ * Resolves the SVG fill color for a well. Optional — defaults to a filled
100
+ * swatch for populated wells and the empty-well token for the rest, which is
101
+ * enough for read-only or single-category views.
102
+ */
103
+ colorForWell?: (well: T | undefined, wellId: WellId) => string;
104
+ /** Builds an empty record when a well is freshly created. Defaults to `{}`. */
105
+ emptyEntry?: (wellId: WellId) => T;
22
106
  /**
23
107
  * Merges the staged form record onto an existing well record on Apply.
24
108
  * Defaults to a shallow merge (form keys overwrite existing keys when set).
@@ -28,14 +112,37 @@ export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends
28
112
  isPopulated?: (row: T) => boolean;
29
113
  /** Select field cycled when double-clicking a single well, e.g. role painting. */
30
114
  cycleFieldOnWellDoubleClick?: keyof T & string;
115
+ /**
116
+ * Names a `select` field whose options appear as a swatch strip anchored above
117
+ * the selection, so a value can be painted across it in one click without a
118
+ * trip to the form. Swatch colours come from each option's `swatch`.
119
+ *
120
+ * Applies immediately — this is a shortcut for the common case, not a staged
121
+ * edit. Off unless set.
122
+ */
123
+ quickPaintField?: keyof T & string;
124
+ /**
125
+ * Controlled staged form record. Pair with `onStagedChange` to prefill the
126
+ * form, read what's staged, or drive Apply from outside. When omitted the
127
+ * editor owns staged state internally.
128
+ */
129
+ staged?: Partial<T>;
130
+ onStagedChange?: (next: Partial<T>) => void;
31
131
  /** Optional header title (e.g. plate set name). */
32
132
  title?: string;
33
133
  /** Optional badges shown in the header next to the title. */
34
134
  badges?: React.ReactNode;
35
- /** Optional banner (e.g. import/error alert) shown above the layout. */
135
+ /** Optional banner (e.g. import/error alert) shown above the whole layout. */
36
136
  banner?: React.ReactNode;
37
- /** Legend block rendered under the form column. */
137
+ /** Legend block. Position controlled by `legendPlacement`. */
38
138
  legend?: React.ReactNode;
139
+ /**
140
+ * Where `legend` renders. `"form"` (default) puts it under the form column,
141
+ * separated by a divider. `"plate"` puts it under the grid inside the plate
142
+ * card, stacked above anything in `plateFooter`. With `hideForm`, `"form"`
143
+ * drops the legend entirely — use `"plate"` there.
144
+ */
145
+ legendPlacement?: "form" | "plate";
39
146
  /** Form helper slot rendered between fields and the action row. */
40
147
  formExtras?: React.ReactNode;
41
148
  /**
@@ -44,12 +151,41 @@ export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends
44
151
  * source palette. The legend slot still renders beneath the replacement.
45
152
  */
46
153
  formSlot?: React.ReactNode;
47
- /** Footer actions (e.g. Save, Back). */
154
+ /** Hides the form column entirely, leaving grid (+ manifest). */
155
+ hideForm?: boolean;
156
+ /**
157
+ * Where the metadata form sits relative to the plate grid. `"start"` /
158
+ * `"end"` put it beside the grid (subject to `stackAt`); `"top"` / `"bottom"`
159
+ * stack it full-width at every breakpoint. Defaults to `"start"`.
160
+ */
161
+ formPlacement?: PlateMapEditorFormPlacement;
162
+ /**
163
+ * Viewport width at which the form and grid sit side by side. Below it they
164
+ * always stack full-width. Ignored when `formPlacement` is `"top"`/`"bottom"`.
165
+ * Defaults to `"md"`.
166
+ */
167
+ stackAt?: PlateMapEditorStackAt;
168
+ /**
169
+ * Width of the form column at and above `stackAt`. Any CSS length (`"420px"`,
170
+ * `"28rem"`, `"30%"`) or a number treated as px. Defaults to `"360px"`.
171
+ * Below `stackAt` the form is always full-width regardless of this value.
172
+ */
173
+ formWidth?: number | string;
174
+ /** Footer actions (e.g. Save, Back), rendered at the very bottom of the editor. */
48
175
  footer?: React.ReactNode;
49
176
  /** Title for the plate grid panel. */
50
177
  plateTitle?: React.ReactNode;
51
- /** Optional controls shown next to the built-in actions menu. */
178
+ /** Optional controls shown above the grid, inside the plate card. */
52
179
  plateToolbar?: React.ReactNode;
180
+ /** Slot rendered above the grid's toolbar, scoped to the plate card only. */
181
+ plateBanner?: React.ReactNode;
182
+ /**
183
+ * Content for the plate card's footer — the one slot below the grid. Renders
184
+ * as a real `CardFooter`, so it picks up the card's footer styling. Use it
185
+ * for per-plate actions or a summary; for the editor-wide action row use
186
+ * `footer`.
187
+ */
188
+ plateFooter?: React.ReactNode;
53
189
  /** User-provided plates available for this editor. Barcodes are never generated by the editor. */
54
190
  plates?: PlateMapPlateOption[];
55
191
  activePlateId?: string;
@@ -65,6 +201,15 @@ export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends
65
201
  plateSelectorVariant?: PlateMapPlateSelectorVariant;
66
202
  /** Row field used to stamp the active user-provided barcode onto edited wells. Defaults to `plateBarcode`. */
67
203
  plateBarcodeField?: keyof T & string;
204
+ /**
205
+ * Which plates Apply/Clear write to. `"active-plate"` (default) touches only
206
+ * the plate on screen; `"all-plates"` writes the same well positions on every
207
+ * plate in `plates`, each row stamped with its own barcode.
208
+ *
209
+ * For a UI offering both side by side, use `usePlateMapEditorState` directly —
210
+ * `applyStagedToSelection("all-plates")` takes a per-call override.
211
+ */
212
+ applyScope?: PlateMapApplyScope;
68
213
  /** Header for the automatic manifest barcode column. */
69
214
  plateBarcodeColumnHeader?: string;
70
215
  /** Hide the automatic manifest barcode column when plate-scoped editing is active. */
@@ -81,6 +226,13 @@ export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends
81
226
  emptyWellFillColor?: string | null;
82
227
  /** Well shape forwarded to `PlatePaintGrid`. Defaults to `"rect"`. */
83
228
  wellShape?: WellShape;
229
+ /**
230
+ * How a selected well is filled. `"well"` (default) keeps the well's own
231
+ * colour and shows selection via the ring, so the result of an Apply is
232
+ * visible while the wells are still selected. `"selection"` replaces the
233
+ * fill with the selection tint.
234
+ */
235
+ selectionFillMode?: "selection" | "well";
84
236
  /** When true, wraps the grid in a card-like plate frame (rounded + border + soft shadow). */
85
237
  framedPlate?: boolean;
86
238
  /**
@@ -93,14 +245,118 @@ export interface PlateMapEditorProps<T extends WellRecord = WellRecord> extends
93
245
  highlightedWellIds?: ReadonlySet<WellId>;
94
246
  /** Fires whenever the currently hovered well changes (null on leave). */
95
247
  onHoveredWellChange?: (wellId: WellId | null) => void;
96
- /** Enables the filter popover on the manifest table. */
248
+ /** Hides the grid's built-in "Select all" / "Deselect all" links. */
249
+ hideSelectionControls?: boolean;
250
+ /**
251
+ * Overrides every user-facing string the editor and its manifest render —
252
+ * the single place to localise. Omitted keys fall back to English defaults.
253
+ *
254
+ * Does not cover `plateTitle` / `manifestTitle` (own props, `ReactNode`) or
255
+ * the import/export menu's labels (`importCsvLabel` and friends, inherited
256
+ * from `PlateMapActionsMenuProps`).
257
+ */
258
+ labels?: PlateMapEditorLabels;
259
+ /** Hides the sample manifest panel entirely. */
260
+ hideManifest?: boolean;
261
+ /**
262
+ * Fully replaces the manifest panel's body, keeping the card and heading.
263
+ * Use for a bespoke summary table; pair with `hideManifest` to drop the
264
+ * region altogether.
265
+ */
266
+ manifestSlot?: React.ReactNode;
267
+ /** Heading for the manifest card. Defaults to `"Sample manifest"`. */
268
+ manifestTitle?: React.ReactNode;
269
+ /** Manifest table behaviour. See {@link PlateMapEditorManifestOptions}. */
270
+ manifest?: PlateMapEditorManifestOptions;
271
+ /** @deprecated Use `manifest={{ filterable }}`. */
97
272
  manifestFilterable?: boolean;
98
- /** Enables the group-by selector on the manifest table. */
273
+ /** @deprecated Use `manifest={{ groupable }}`. */
99
274
  manifestGroupable?: boolean;
100
275
  autoScaleGrid?: boolean;
101
276
  minCellSize?: number;
102
277
  maxCellSize?: number;
278
+ /** Applied to the editor root. */
103
279
  className?: string;
280
+ /** Inline styles for the editor root. */
281
+ style?: React.CSSProperties;
282
+ /** Per-region class hooks. See {@link PlateMapEditorClassNames}. */
283
+ classNames?: PlateMapEditorClassNames;
284
+ /**
285
+ * Imperative handle for driving staged edits from outside the editor.
286
+ * See {@link PlateMapEditorHandle}.
287
+ */
288
+ ref?: React.Ref<PlateMapEditorHandle<T>>;
104
289
  }
105
- export declare function PlateMapEditor<T extends WellRecord = WellRecord>({ format, rows, columns, values, onChange, selection, onSelectionChange, fields, tableColumns, colorForWell, emptyEntry, mergeOnApply, isPopulated, cycleFieldOnWellDoubleClick, title, badges, banner, legend, formExtras, formSlot, footer, plateTitle, plateToolbar, plates, activePlateId, onPlateChange, onAddPlate, 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, }: PlateMapEditorProps<T>): import("react/jsx-runtime").JSX.Element;
290
+ /**
291
+ * Turnkey plate-map editing surface: a metadata form, an interactive plate
292
+ * grid, and a sample manifest, wired together with the staged-edit controller
293
+ * in {@link usePlateMapEditorState}.
294
+ *
295
+ * ## Choosing a level
296
+ *
297
+ * | You need | Use |
298
+ * | --- | --- |
299
+ * | The standard surface, tuned by props | `PlateMapEditor` (this) |
300
+ * | A layout these props can't express | {@link usePlateMapEditorState} + `PlateMapForm` / `PlateMapGrid` / `PlateMapManifest` |
301
+ * | One region only, wired yourself | The primitives directly |
302
+ *
303
+ * Dropping to the hook keeps the apply/clear semantics, plate scoping, and
304
+ * barcode stamping — you only take over layout.
305
+ *
306
+ * ## Customization map
307
+ *
308
+ * - **Layout** — `formPlacement` (`start`/`end`/`top`/`bottom`), `stackAt`,
309
+ * `formWidth`, `hideForm`, `hideManifest`.
310
+ * - **Slots** — `title`, `badges`, `banner` (whole editor), `plateBanner`
311
+ * (plate card only), `plateToolbar` (above grid), `plateFooter` (the plate
312
+ * card's footer), `footer` (editor-wide action row), `legend` +
313
+ * `legendPlacement`, `formExtras`, `formSlot` (replaces the form),
314
+ * `manifestSlot` (replaces the manifest body).
315
+ * - **Edit semantics** — `mergeOnApply`, `emptyEntry`, `isPopulated`,
316
+ * `cycleFieldOnWellDoubleClick`, and `staged` / `onStagedChange` to make the
317
+ * staged record controlled.
318
+ * - **Labels** — one `labels` object covers every string the editor and its
319
+ * manifest render. `plateTitle` / `manifestTitle` and the import/export menu
320
+ * labels stay separate (they are `ReactNode` slots, not plain text).
321
+ * - **Styling** — `className` / `style` on the root plus one `classNames` map
322
+ * for the regions. Each card also carries
323
+ * `data-plate-map-region="form|plate|manifest"` for CSS targeting.
324
+ * - **Manifest** — one `manifest` options object; `hideManifest`,
325
+ * `manifestTitle` and `manifestSlot` stay top-level.
326
+ *
327
+ * ## Responsiveness
328
+ *
329
+ * The editor responds to **its container's** width, not the viewport's, so it
330
+ * lays out correctly inside a narrow panel, split pane, or drawer on a wide
331
+ * screen. `stackAt` names a container width (`sm` 640 / `md` 768 / `lg` 1024 /
332
+ * `xl` 1280 / `never`); below it the form and grid stack full-width. A plate
333
+ * too dense to fit scrolls inside its own container rather than widening the
334
+ * page.
335
+ *
336
+ * @example Default surface
337
+ * ```tsx
338
+ * <PlateMapEditor
339
+ * format="96"
340
+ * values={values}
341
+ * onChange={setValues}
342
+ * selection={selection}
343
+ * onSelectionChange={setSelection}
344
+ * fields={FIELDS}
345
+ * tableColumns={COLUMNS}
346
+ * />
347
+ * ```
348
+ *
349
+ * @example Grid-only, form on the right, localised
350
+ * ```tsx
351
+ * <PlateMapEditor
352
+ * {...base}
353
+ * formPlacement="end"
354
+ * stackAt="lg"
355
+ * formWidth="22rem"
356
+ * hideManifest
357
+ * labels={{ apply: "Appliquer", clearWells: "Vider les puits" }}
358
+ * />
359
+ * ```
360
+ */
361
+ export declare function PlateMapEditor<T extends WellRecord = WellRecord>({ format, rows, columns, values, onChange, selection, onSelectionChange, fields, tableColumns, colorForWell, emptyEntry, mergeOnApply, isPopulated, cycleFieldOnWellDoubleClick, quickPaintField, staged: controlledStaged, onStagedChange, title, badges, banner, legend, legendPlacement, formExtras, formSlot, hideForm, formPlacement, stackAt, formWidth, footer, plateTitle, plateToolbar, plateBanner, plateFooter, plates, activePlateId, onPlateChange, onAddPlate, 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, manifest, 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, }: PlateMapEditorProps<T>): import("react/jsx-runtime").JSX.Element;
106
362
  export { Badge as PlateBadge };