@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":"WellMetadataForm.js","sources":["../../../../src/components/composed/PlateMapEditor/WellMetadataForm.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport type { WellField, WellRecord, WellSelectOption } from \"./types\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n Combobox,\n ComboboxChip,\n ComboboxChips,\n ComboboxChipsInput,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxItem,\n ComboboxList,\n ComboboxValue,\n useComboboxAnchor,\n} from \"@/components/ui/combobox\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { cn } from \"@/lib/utils\";\n\nfunction MultiSelectControl({\n id,\n value,\n options,\n placeholder,\n onChange,\n}: {\n id: string;\n value: string[];\n options: WellSelectOption[];\n placeholder?: string;\n onChange: (next: string[]) => void;\n}) {\n const anchorRef = useComboboxAnchor();\n const labelByValue = React.useMemo(() => {\n const m = new Map<string, string>();\n options.forEach((o) => m.set(o.value, o.label));\n return m;\n }, [options]);\n\n return (\n <Combobox multiple items={options.map((o) => o.value)} value={value} onValueChange={onChange}>\n <ComboboxChips ref={anchorRef} className=\"min-h-9 w-full\">\n <ComboboxValue>\n {(items: string[]) =>\n items.map((item) => <ComboboxChip key={item}>{labelByValue.get(item) ?? item}</ComboboxChip>)\n }\n </ComboboxValue>\n <ComboboxChipsInput id={id} placeholder={placeholder ?? \"Select…\"} />\n </ComboboxChips>\n <ComboboxContent anchor={anchorRef}>\n <ComboboxEmpty>No options.</ComboboxEmpty>\n <ComboboxList>\n {(item: string) => (\n <ComboboxItem key={item} value={item}>\n <span className=\"inline-flex items-center gap-2\">\n {(() => {\n const opt = options.find((o) => o.value === item);\n return opt?.swatch ? (\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: opt.swatch }}\n aria-hidden\n />\n ) : null;\n })()}\n {labelByValue.get(item) ?? item}\n </span>\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n );\n}\n\nexport interface WellMetadataFormProps<T extends WellRecord = WellRecord> {\n fields: WellField<T>[];\n /** Staged values used to apply across the selection. */\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 /** Optional extra slot rendered between fields and action row. */\n extras?: React.ReactNode;\n className?: string;\n}\n\nconst INPUT_TYPE_BY_KIND: Record<string, string> = {\n number: \"number\",\n integer: \"number\",\n date: \"date\",\n datetime: \"datetime-local\",\n time: \"time\",\n};\n\nfunction resolveInputType(kind: string): string {\n return INPUT_TYPE_BY_KIND[kind] ?? \"text\";\n}\n\nfunction parseInputValue(kind: string, raw: string): unknown {\n if (raw === \"\") return undefined;\n if (kind === \"number\") {\n const num = parseFloat(raw);\n return Number.isFinite(num) ? num : raw;\n }\n if (kind === \"integer\") {\n const num = parseInt(raw, 10);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\nexport function WellMetadataForm<T extends WellRecord = WellRecord>({\n fields,\n value,\n onChange,\n selectionSize,\n onApply,\n onClear,\n applyLabel = \"Apply\",\n clearLabel = \"Clear wells\",\n extras,\n className,\n}: WellMetadataFormProps<T>) {\n const setField = (key: keyof T & string, next: unknown) => {\n onChange({ ...value, [key]: next } as Partial<T>);\n };\n\n const renderLabel = (field: WellField<T>) => (\n <Label htmlFor={`field-${field.key}`} className=\"gap-1.5\">\n {field.icon ? <span className=\"inline-flex text-muted-foreground [&_svg]:size-3.5\">{field.icon}</span> : null}\n {field.label}\n </Label>\n );\n\n const renderCustom = (field: WellField<T>, fieldValue: unknown) => (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n {field.render!({\n value: fieldValue,\n onChange: (next) => setField(field.key, next),\n selectionSize,\n })}\n </div>\n );\n\n const renderSelect = (field: WellField<T>, fieldValue: unknown) => (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n <Select value={(fieldValue as string | undefined) ?? \"\"} onValueChange={(v) => setField(field.key, v)}>\n <SelectTrigger id={`field-${field.key}`} size=\"sm\" className=\"w-full\">\n <SelectValue placeholder={field.placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n <span className=\"inline-flex items-center gap-2\">\n {opt.swatch ? (\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: opt.swatch }}\n aria-hidden\n />\n ) : null}\n {opt.label}\n </span>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n\n const renderMultiSelect = (field: WellField<T>, fieldValue: unknown) => {\n const current = Array.isArray(fieldValue) ? (fieldValue as string[]) : [];\n return (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n <MultiSelectControl\n id={`field-${field.key}`}\n value={current}\n options={field.options ?? []}\n placeholder={field.placeholder}\n onChange={(next) => setField(field.key, next.length === 0 ? undefined : next)}\n />\n </div>\n );\n };\n\n const renderBoolean = (field: WellField<T>, fieldValue: unknown) => {\n const checked = !!fieldValue;\n if (field.boolStyle === \"switch\") {\n return (\n <div key={field.key} className=\"flex items-center justify-between gap-2\">\n {renderLabel(field)}\n <Switch\n id={`field-${field.key}`}\n checked={checked}\n onCheckedChange={(c) => setField(field.key, c)}\n />\n </div>\n );\n }\n return (\n <div key={field.key} className=\"flex items-center gap-2\">\n <Checkbox\n id={`field-${field.key}`}\n checked={checked}\n onCheckedChange={(c) => setField(field.key, c === true)}\n />\n {renderLabel(field)}\n </div>\n );\n };\n\n const renderInput = (field: WellField<T>, fieldValue: unknown) => (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n <Input\n id={`field-${field.key}`}\n type={resolveInputType(field.kind)}\n step={field.kind === \"integer\" ? 1 : undefined}\n placeholder={field.placeholder}\n value={(fieldValue as string | number | undefined) ?? \"\"}\n onChange={(e) => setField(field.key, parseInputValue(field.kind, e.target.value))}\n />\n </div>\n );\n\n const renderField = (field: WellField<T>) => {\n const fieldValue = value[field.key];\n if (field.render) return renderCustom(field, fieldValue);\n if (field.kind === \"select\") return renderSelect(field, fieldValue);\n if (field.kind === \"multiselect\") return renderMultiSelect(field, fieldValue);\n if (field.kind === \"boolean\") return renderBoolean(field, fieldValue);\n return renderInput(field, fieldValue);\n };\n\n return (\n <div data-slot=\"well-metadata-form\" className={cn(\"flex flex-col gap-3\", className)}>\n <div className=\"text-sm font-medium\">\n {selectionSize > 0 ? `Apply to ${selectionSize} well${selectionSize === 1 ? \"\" : \"s\"}` : \"Select wells to edit\"}\n </div>\n\n {fields.map(renderField)}\n\n {extras}\n\n <div className=\"mt-auto flex gap-2 pt-1\">\n <Button variant=\"default\" size=\"sm\" disabled={selectionSize === 0} onClick={onApply}>\n {applyLabel}\n </Button>\n <Button variant=\"outline\" size=\"sm\" disabled={selectionSize === 0} onClick={onClear}>\n {clearLabel}\n </Button>\n </div>\n </div>\n );\n}\n"],"names":["MultiSelectControl","id","value","options","placeholder","onChange","anchorRef","useComboboxAnchor","labelByValue","React","m","o","jsxs","Combobox","ComboboxChips","jsx","ComboboxValue","items","item","ComboboxChip","ComboboxChipsInput","ComboboxContent","ComboboxEmpty","ComboboxList","ComboboxItem","opt","INPUT_TYPE_BY_KIND","resolveInputType","kind","parseInputValue","raw","num","WellMetadataForm","fields","selectionSize","onApply","onClear","applyLabel","clearLabel","extras","className","setField","key","next","renderLabel","field","Label","renderCustom","fieldValue","renderSelect","Select","v","SelectTrigger","SelectValue","SelectContent","SelectItem","renderMultiSelect","current","renderBoolean","checked","Switch","c","Checkbox","renderInput","Input","e","renderField","cn","Button"],"mappings":"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"}
1
+ {"version":3,"file":"WellMetadataForm.js","sources":["../../../../src/components/composed/PlateMapEditor/WellMetadataForm.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport type { WellField, WellRecord, WellSelectOption } from \"./types\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport {\n Combobox,\n ComboboxChip,\n ComboboxChips,\n ComboboxChipsInput,\n ComboboxContent,\n ComboboxEmpty,\n ComboboxItem,\n ComboboxList,\n ComboboxValue,\n useComboboxAnchor,\n} from \"@/components/ui/combobox\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { cn } from \"@/lib/utils\";\n\nfunction MultiSelectControl({\n id,\n value,\n options,\n placeholder,\n onChange,\n}: {\n id: string;\n value: string[];\n options: WellSelectOption[];\n placeholder?: string;\n onChange: (next: string[]) => void;\n}) {\n const anchorRef = useComboboxAnchor();\n const labelByValue = React.useMemo(() => {\n const m = new Map<string, string>();\n options.forEach((o) => m.set(o.value, o.label));\n return m;\n }, [options]);\n\n return (\n <Combobox multiple items={options.map((o) => o.value)} value={value} onValueChange={onChange}>\n <ComboboxChips ref={anchorRef} className=\"min-h-9 w-full\">\n <ComboboxValue>\n {(items: string[]) =>\n items.map((item) => <ComboboxChip key={item}>{labelByValue.get(item) ?? item}</ComboboxChip>)\n }\n </ComboboxValue>\n <ComboboxChipsInput id={id} placeholder={placeholder ?? \"Select…\"} />\n </ComboboxChips>\n <ComboboxContent anchor={anchorRef}>\n <ComboboxEmpty>No options.</ComboboxEmpty>\n <ComboboxList>\n {(item: string) => (\n <ComboboxItem key={item} value={item}>\n <span className=\"inline-flex items-center gap-2\">\n {(() => {\n const opt = options.find((o) => o.value === item);\n return opt?.swatch ? (\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: opt.swatch }}\n aria-hidden\n />\n ) : null;\n })()}\n {labelByValue.get(item) ?? item}\n </span>\n </ComboboxItem>\n )}\n </ComboboxList>\n </ComboboxContent>\n </Combobox>\n );\n}\n\nexport interface WellMetadataFormProps<T extends WellRecord = WellRecord> {\n fields: WellField<T>[];\n /** Staged values used to apply across the selection. */\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. Defaults to `\"Select wells to edit\"`. */\n selectionEmptyLabel?: string;\n /** Heading shown with a selection. Defaults to `` `Apply to ${n} wells` ``. */\n selectionCountLabel?: (selectionSize: number) => string;\n /** Optional extra slot rendered between fields and action row. */\n extras?: React.ReactNode;\n className?: string;\n}\n\nconst INPUT_TYPE_BY_KIND: Record<string, string> = {\n number: \"number\",\n integer: \"number\",\n date: \"date\",\n datetime: \"datetime-local\",\n time: \"time\",\n};\n\nfunction resolveInputType(kind: string): string {\n return INPUT_TYPE_BY_KIND[kind] ?? \"text\";\n}\n\nfunction parseInputValue(kind: string, raw: string): unknown {\n if (raw === \"\") return undefined;\n if (kind === \"number\") {\n const num = parseFloat(raw);\n return Number.isFinite(num) ? num : raw;\n }\n if (kind === \"integer\") {\n const num = parseInt(raw, 10);\n return Number.isFinite(num) ? num : raw;\n }\n return raw;\n}\n\nexport function WellMetadataForm<T extends WellRecord = WellRecord>({\n fields,\n value,\n onChange,\n selectionSize,\n onApply,\n onClear,\n applyLabel = \"Apply\",\n clearLabel = \"Clear wells\",\n selectionEmptyLabel = \"Select wells to edit\",\n selectionCountLabel = (n: number) => `Apply to ${n} well${n === 1 ? \"\" : \"s\"}`,\n extras,\n className,\n}: WellMetadataFormProps<T>) {\n const setField = (key: keyof T & string, next: unknown) => {\n onChange({ ...value, [key]: next } as Partial<T>);\n };\n\n const renderLabel = (field: WellField<T>) => (\n <Label htmlFor={`field-${field.key}`} className=\"gap-1.5\">\n {field.icon ? <span className=\"inline-flex text-muted-foreground [&_svg]:size-3.5\">{field.icon}</span> : null}\n {field.label}\n </Label>\n );\n\n const renderCustom = (field: WellField<T>, fieldValue: unknown) => (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n {field.render!({\n value: fieldValue,\n onChange: (next) => setField(field.key, next),\n selectionSize,\n })}\n </div>\n );\n\n const renderSelect = (field: WellField<T>, fieldValue: unknown) => (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n <Select value={(fieldValue as string | undefined) ?? \"\"} onValueChange={(v) => setField(field.key, v)}>\n <SelectTrigger id={`field-${field.key}`} size=\"sm\" className=\"w-full\">\n <SelectValue placeholder={field.placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n {(field.options ?? []).map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n <span className=\"inline-flex items-center gap-2\">\n {opt.swatch ? (\n <span\n className=\"inline-block h-2.5 w-2.5 rounded-sm border border-foreground/20\"\n style={{ backgroundColor: opt.swatch }}\n aria-hidden\n />\n ) : null}\n {opt.label}\n </span>\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n\n const renderMultiSelect = (field: WellField<T>, fieldValue: unknown) => {\n const current = Array.isArray(fieldValue) ? (fieldValue as string[]) : [];\n return (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n <MultiSelectControl\n id={`field-${field.key}`}\n value={current}\n options={field.options ?? []}\n placeholder={field.placeholder}\n onChange={(next) => setField(field.key, next.length === 0 ? undefined : next)}\n />\n </div>\n );\n };\n\n const renderBoolean = (field: WellField<T>, fieldValue: unknown) => {\n const checked = !!fieldValue;\n if (field.boolStyle === \"switch\") {\n return (\n <div key={field.key} className=\"flex items-center justify-between gap-2\">\n {renderLabel(field)}\n <Switch id={`field-${field.key}`} checked={checked} onCheckedChange={(c) => setField(field.key, c)} />\n </div>\n );\n }\n return (\n <div key={field.key} className=\"flex items-center gap-2\">\n <Checkbox\n id={`field-${field.key}`}\n checked={checked}\n onCheckedChange={(c) => setField(field.key, c === true)}\n />\n {renderLabel(field)}\n </div>\n );\n };\n\n const renderInput = (field: WellField<T>, fieldValue: unknown) => (\n <div key={field.key} className=\"flex flex-col gap-1.5\">\n {renderLabel(field)}\n <Input\n id={`field-${field.key}`}\n type={resolveInputType(field.kind)}\n step={field.kind === \"integer\" ? 1 : undefined}\n placeholder={field.placeholder}\n value={(fieldValue as string | number | undefined) ?? \"\"}\n onChange={(e) => setField(field.key, parseInputValue(field.kind, e.target.value))}\n />\n </div>\n );\n\n const renderField = (field: WellField<T>) => {\n const fieldValue = value[field.key];\n if (field.render) return renderCustom(field, fieldValue);\n if (field.kind === \"select\") return renderSelect(field, fieldValue);\n if (field.kind === \"multiselect\") return renderMultiSelect(field, fieldValue);\n if (field.kind === \"boolean\") return renderBoolean(field, fieldValue);\n return renderInput(field, fieldValue);\n };\n\n return (\n <div data-slot=\"well-metadata-form\" className={cn(\"flex flex-col gap-3\", className)}>\n <div className=\"text-sm font-medium\">\n {selectionSize > 0 ? selectionCountLabel(selectionSize) : selectionEmptyLabel}\n </div>\n\n {fields.map(renderField)}\n\n {extras}\n\n <div className=\"mt-auto flex gap-2 pt-1\">\n <Button variant=\"default\" size=\"sm\" disabled={selectionSize === 0} onClick={onApply}>\n {applyLabel}\n </Button>\n <Button variant=\"outline\" size=\"sm\" disabled={selectionSize === 0} onClick={onClear}>\n {clearLabel}\n </Button>\n </div>\n </div>\n );\n}\n"],"names":["MultiSelectControl","id","value","options","placeholder","onChange","anchorRef","useComboboxAnchor","labelByValue","React","m","o","jsxs","Combobox","ComboboxChips","jsx","ComboboxValue","items","item","ComboboxChip","ComboboxChipsInput","ComboboxContent","ComboboxEmpty","ComboboxList","ComboboxItem","opt","INPUT_TYPE_BY_KIND","resolveInputType","kind","parseInputValue","raw","num","WellMetadataForm","fields","selectionSize","onApply","onClear","applyLabel","clearLabel","selectionEmptyLabel","selectionCountLabel","n","extras","className","setField","key","next","renderLabel","field","Label","renderCustom","fieldValue","renderSelect","Select","v","SelectTrigger","SelectValue","SelectContent","SelectItem","renderMultiSelect","current","renderBoolean","checked","Switch","c","Checkbox","renderInput","Input","e","renderField","cn","Button"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),i=require("../../../lib/utils.cjs"),g=6;function f({options:l,onPick:s,activeValue:t,selectionSize:n,label:c="Quick paint",fallbackColor:u="var(--surface-container)",className:o}){if(l.length===0)return null;const d=Math.min(l.length,g);return r.jsxs("div",{"data-slot":"well-quick-paint",role:"group","aria-label":c,className:i.cn("flex flex-col gap-1 rounded-lg border bg-popover p-1.5 shadow-elevation-3",o),children:[r.jsx("div",{className:"grid gap-1",style:{gridTemplateColumns:`repeat(${d}, minmax(0, 1fr))`},children:l.map(e=>{const a=t!==void 0&&e.value===t;return r.jsx("button",{type:"button",title:e.label,"aria-label":e.label,"aria-pressed":a,onClick:()=>s(e.value),className:i.cn("inline-flex size-8 items-center justify-center rounded-md transition-colors","hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",a&&"bg-accent"),children:r.jsx("span",{"aria-hidden":!0,className:i.cn("size-5 rounded-full border border-foreground/20",a&&"ring-2 ring-ring ring-offset-1 ring-offset-popover"),style:{backgroundColor:e.swatch??u}})},e.value)})}),n===void 0?null:r.jsxs("span",{className:"px-0.5 text-center text-[0.7rem] whitespace-nowrap text-muted-foreground",children:[n," ",n===1?"well":"wells"]})]})}exports.WellQuickPaint=f;
2
+ //# sourceMappingURL=WellQuickPaint.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WellQuickPaint.cjs","sources":["../../../../src/components/composed/PlateMapEditor/WellQuickPaint.tsx"],"sourcesContent":["import type { WellSelectOption } from \"./types\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Swatches grow to this many across before wrapping to another row. */\nconst QUICK_PAINT_MAX_COLUMNS = 6;\n\nexport interface WellQuickPaintProps {\n /** Swatch options, normally a `select` field's `options`. */\n options: WellSelectOption[];\n /** Fired with the chosen option's value. */\n onPick: (value: string) => void;\n /** Currently applied value across the selection, when they all agree. */\n activeValue?: string;\n /** Count shown alongside the swatches, e.g. \"6 wells\". */\n selectionSize?: number;\n /** Accessible name for the group. Defaults to `\"Quick paint\"`. */\n label?: string;\n /** Fallback colour for an option with no `swatch`. */\n fallbackColor?: string;\n className?: string;\n}\n\n/**\n * Swatch strip for painting a value across the current selection in one click.\n *\n * Presentational and position-agnostic — `PlatePaintGrid` anchors it above the\n * selection. Bring your own container to use it anywhere else.\n */\nexport function WellQuickPaint({\n options,\n onPick,\n activeValue,\n selectionSize,\n label = \"Quick paint\",\n fallbackColor = \"var(--surface-container)\",\n className,\n}: WellQuickPaintProps) {\n if (options.length === 0) return null;\n\n // Grows to six across, then wraps — twelve categorical colours land as a\n // tidy 6x2 rather than a strip wider than the plate.\n const columns = Math.min(options.length, QUICK_PAINT_MAX_COLUMNS);\n\n return (\n <div\n data-slot=\"well-quick-paint\"\n role=\"group\"\n aria-label={label}\n className={cn(\n \"flex flex-col gap-1 rounded-lg border bg-popover p-1.5 shadow-elevation-3\",\n className,\n )}\n >\n <div\n className=\"grid gap-1\"\n style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}\n >\n {options.map((option) => {\n const isActive = activeValue !== undefined && option.value === activeValue;\n return (\n <button\n key={option.value}\n type=\"button\"\n title={option.label}\n aria-label={option.label}\n aria-pressed={isActive}\n onClick={() => onPick(option.value)}\n className={cn(\n \"inline-flex size-8 items-center justify-center rounded-md transition-colors\",\n \"hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none\",\n isActive && \"bg-accent\",\n )}\n >\n <span\n aria-hidden\n className={cn(\n \"size-5 rounded-full border border-foreground/20\",\n isActive && \"ring-2 ring-ring ring-offset-1 ring-offset-popover\",\n )}\n style={{ backgroundColor: option.swatch ?? fallbackColor }}\n />\n </button>\n );\n })}\n </div>\n {selectionSize === undefined ? null : (\n <span className=\"px-0.5 text-center text-[0.7rem] whitespace-nowrap text-muted-foreground\">\n {selectionSize} {selectionSize === 1 ? \"well\" : \"wells\"}\n </span>\n )}\n </div>\n );\n}\n"],"names":["QUICK_PAINT_MAX_COLUMNS","WellQuickPaint","options","onPick","activeValue","selectionSize","label","fallbackColor","className","columns","jsxs","cn","jsx","option","isActive"],"mappings":"yJAKMA,EAA0B,EAwBzB,SAASC,EAAe,CAC7B,QAAAC,EACA,OAAAC,EACA,YAAAC,EACA,cAAAC,EACA,MAAAC,EAAQ,cACR,cAAAC,EAAgB,2BAChB,UAAAC,CACF,EAAwB,CACtB,GAAIN,EAAQ,SAAW,EAAG,OAAO,KAIjC,MAAMO,EAAU,KAAK,IAAIP,EAAQ,OAAQF,CAAuB,EAEhE,OACEU,EAAAA,KAAC,MAAA,CACC,YAAU,mBACV,KAAK,QACL,aAAYJ,EACZ,UAAWK,EAAAA,GACT,4EACAH,CAAA,EAGF,SAAA,CAAAI,EAAAA,IAAC,MAAA,CACC,UAAU,aACV,MAAO,CAAE,oBAAqB,UAAUH,CAAO,mBAAA,EAE9C,SAAAP,EAAQ,IAAKW,GAAW,CACvB,MAAMC,EAAWV,IAAgB,QAAaS,EAAO,QAAUT,EAC/D,OACEQ,EAAAA,IAAC,SAAA,CAEC,KAAK,SACL,MAAOC,EAAO,MACd,aAAYA,EAAO,MACnB,eAAcC,EACd,QAAS,IAAMX,EAAOU,EAAO,KAAK,EAClC,UAAWF,EAAAA,GACT,8EACA,0FACAG,GAAY,WAAA,EAGd,SAAAF,EAAAA,IAAC,OAAA,CACC,cAAW,GACX,UAAWD,EAAAA,GACT,kDACAG,GAAY,oDAAA,EAEd,MAAO,CAAE,gBAAiBD,EAAO,QAAUN,CAAA,CAAc,CAAA,CAC3D,EAnBKM,EAAO,KAAA,CAsBlB,CAAC,CAAA,CAAA,EAEFR,IAAkB,OAAY,KAC7BK,EAAAA,KAAC,OAAA,CAAK,UAAU,2EACb,SAAA,CAAAL,EAAc,IAAEA,IAAkB,EAAI,OAAS,OAAA,CAAA,CAClD,CAAA,CAAA,CAAA,CAIR"}
@@ -0,0 +1,23 @@
1
+ import { WellSelectOption } from './types';
2
+ export interface WellQuickPaintProps {
3
+ /** Swatch options, normally a `select` field's `options`. */
4
+ options: WellSelectOption[];
5
+ /** Fired with the chosen option's value. */
6
+ onPick: (value: string) => void;
7
+ /** Currently applied value across the selection, when they all agree. */
8
+ activeValue?: string;
9
+ /** Count shown alongside the swatches, e.g. "6 wells". */
10
+ selectionSize?: number;
11
+ /** Accessible name for the group. Defaults to `"Quick paint"`. */
12
+ label?: string;
13
+ /** Fallback colour for an option with no `swatch`. */
14
+ fallbackColor?: string;
15
+ className?: string;
16
+ }
17
+ /**
18
+ * Swatch strip for painting a value across the current selection in one click.
19
+ *
20
+ * Presentational and position-agnostic — `PlatePaintGrid` anchors it above the
21
+ * selection. Bring your own container to use it anywhere else.
22
+ */
23
+ export declare function WellQuickPaint({ options, onPick, activeValue, selectionSize, label, fallbackColor, className, }: WellQuickPaintProps): import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,75 @@
1
+ import { jsxs as s, jsx as a } from "react/jsx-runtime";
2
+ import { cn as i } from "../../../lib/utils.js";
3
+ const f = 6;
4
+ function p({
5
+ options: r,
6
+ onPick: o,
7
+ activeValue: t,
8
+ selectionSize: l,
9
+ label: c = "Quick paint",
10
+ fallbackColor: u = "var(--surface-container)",
11
+ className: d
12
+ }) {
13
+ if (r.length === 0) return null;
14
+ const g = Math.min(r.length, f);
15
+ return /* @__PURE__ */ s(
16
+ "div",
17
+ {
18
+ "data-slot": "well-quick-paint",
19
+ role: "group",
20
+ "aria-label": c,
21
+ className: i(
22
+ "flex flex-col gap-1 rounded-lg border bg-popover p-1.5 shadow-elevation-3",
23
+ d
24
+ ),
25
+ children: [
26
+ /* @__PURE__ */ a(
27
+ "div",
28
+ {
29
+ className: "grid gap-1",
30
+ style: { gridTemplateColumns: `repeat(${g}, minmax(0, 1fr))` },
31
+ children: r.map((e) => {
32
+ const n = t !== void 0 && e.value === t;
33
+ return /* @__PURE__ */ a(
34
+ "button",
35
+ {
36
+ type: "button",
37
+ title: e.label,
38
+ "aria-label": e.label,
39
+ "aria-pressed": n,
40
+ onClick: () => o(e.value),
41
+ className: i(
42
+ "inline-flex size-8 items-center justify-center rounded-md transition-colors",
43
+ "hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
44
+ n && "bg-accent"
45
+ ),
46
+ children: /* @__PURE__ */ a(
47
+ "span",
48
+ {
49
+ "aria-hidden": !0,
50
+ className: i(
51
+ "size-5 rounded-full border border-foreground/20",
52
+ n && "ring-2 ring-ring ring-offset-1 ring-offset-popover"
53
+ ),
54
+ style: { backgroundColor: e.swatch ?? u }
55
+ }
56
+ )
57
+ },
58
+ e.value
59
+ );
60
+ })
61
+ }
62
+ ),
63
+ l === void 0 ? null : /* @__PURE__ */ s("span", { className: "px-0.5 text-center text-[0.7rem] whitespace-nowrap text-muted-foreground", children: [
64
+ l,
65
+ " ",
66
+ l === 1 ? "well" : "wells"
67
+ ] })
68
+ ]
69
+ }
70
+ );
71
+ }
72
+ export {
73
+ p as WellQuickPaint
74
+ };
75
+ //# sourceMappingURL=WellQuickPaint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WellQuickPaint.js","sources":["../../../../src/components/composed/PlateMapEditor/WellQuickPaint.tsx"],"sourcesContent":["import type { WellSelectOption } from \"./types\";\n\nimport { cn } from \"@/lib/utils\";\n\n/** Swatches grow to this many across before wrapping to another row. */\nconst QUICK_PAINT_MAX_COLUMNS = 6;\n\nexport interface WellQuickPaintProps {\n /** Swatch options, normally a `select` field's `options`. */\n options: WellSelectOption[];\n /** Fired with the chosen option's value. */\n onPick: (value: string) => void;\n /** Currently applied value across the selection, when they all agree. */\n activeValue?: string;\n /** Count shown alongside the swatches, e.g. \"6 wells\". */\n selectionSize?: number;\n /** Accessible name for the group. Defaults to `\"Quick paint\"`. */\n label?: string;\n /** Fallback colour for an option with no `swatch`. */\n fallbackColor?: string;\n className?: string;\n}\n\n/**\n * Swatch strip for painting a value across the current selection in one click.\n *\n * Presentational and position-agnostic — `PlatePaintGrid` anchors it above the\n * selection. Bring your own container to use it anywhere else.\n */\nexport function WellQuickPaint({\n options,\n onPick,\n activeValue,\n selectionSize,\n label = \"Quick paint\",\n fallbackColor = \"var(--surface-container)\",\n className,\n}: WellQuickPaintProps) {\n if (options.length === 0) return null;\n\n // Grows to six across, then wraps — twelve categorical colours land as a\n // tidy 6x2 rather than a strip wider than the plate.\n const columns = Math.min(options.length, QUICK_PAINT_MAX_COLUMNS);\n\n return (\n <div\n data-slot=\"well-quick-paint\"\n role=\"group\"\n aria-label={label}\n className={cn(\n \"flex flex-col gap-1 rounded-lg border bg-popover p-1.5 shadow-elevation-3\",\n className,\n )}\n >\n <div\n className=\"grid gap-1\"\n style={{ gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))` }}\n >\n {options.map((option) => {\n const isActive = activeValue !== undefined && option.value === activeValue;\n return (\n <button\n key={option.value}\n type=\"button\"\n title={option.label}\n aria-label={option.label}\n aria-pressed={isActive}\n onClick={() => onPick(option.value)}\n className={cn(\n \"inline-flex size-8 items-center justify-center rounded-md transition-colors\",\n \"hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none\",\n isActive && \"bg-accent\",\n )}\n >\n <span\n aria-hidden\n className={cn(\n \"size-5 rounded-full border border-foreground/20\",\n isActive && \"ring-2 ring-ring ring-offset-1 ring-offset-popover\",\n )}\n style={{ backgroundColor: option.swatch ?? fallbackColor }}\n />\n </button>\n );\n })}\n </div>\n {selectionSize === undefined ? null : (\n <span className=\"px-0.5 text-center text-[0.7rem] whitespace-nowrap text-muted-foreground\">\n {selectionSize} {selectionSize === 1 ? \"well\" : \"wells\"}\n </span>\n )}\n </div>\n );\n}\n"],"names":["QUICK_PAINT_MAX_COLUMNS","WellQuickPaint","options","onPick","activeValue","selectionSize","label","fallbackColor","className","columns","jsxs","cn","jsx","option","isActive"],"mappings":";;AAKA,MAAMA,IAA0B;AAwBzB,SAASC,EAAe;AAAA,EAC7B,SAAAC;AAAA,EACA,QAAAC;AAAA,EACA,aAAAC;AAAA,EACA,eAAAC;AAAA,EACA,OAAAC,IAAQ;AAAA,EACR,eAAAC,IAAgB;AAAA,EAChB,WAAAC;AACF,GAAwB;AACtB,MAAIN,EAAQ,WAAW,EAAG,QAAO;AAIjC,QAAMO,IAAU,KAAK,IAAIP,EAAQ,QAAQF,CAAuB;AAEhE,SACE,gBAAAU;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,aAAU;AAAA,MACV,MAAK;AAAA,MACL,cAAYJ;AAAA,MACZ,WAAWK;AAAA,QACT;AAAA,QACAH;AAAA,MAAA;AAAA,MAGF,UAAA;AAAA,QAAA,gBAAAI;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,qBAAqB,UAAUH,CAAO,oBAAA;AAAA,YAE9C,UAAAP,EAAQ,IAAI,CAACW,MAAW;AACvB,oBAAMC,IAAWV,MAAgB,UAAaS,EAAO,UAAUT;AAC/D,qBACE,gBAAAQ;AAAA,gBAAC;AAAA,gBAAA;AAAA,kBAEC,MAAK;AAAA,kBACL,OAAOC,EAAO;AAAA,kBACd,cAAYA,EAAO;AAAA,kBACnB,gBAAcC;AAAA,kBACd,SAAS,MAAMX,EAAOU,EAAO,KAAK;AAAA,kBAClC,WAAWF;AAAA,oBACT;AAAA,oBACA;AAAA,oBACAG,KAAY;AAAA,kBAAA;AAAA,kBAGd,UAAA,gBAAAF;AAAA,oBAAC;AAAA,oBAAA;AAAA,sBACC,eAAW;AAAA,sBACX,WAAWD;AAAA,wBACT;AAAA,wBACAG,KAAY;AAAA,sBAAA;AAAA,sBAEd,OAAO,EAAE,iBAAiBD,EAAO,UAAUN,EAAA;AAAA,oBAAc;AAAA,kBAAA;AAAA,gBAC3D;AAAA,gBAnBKM,EAAO;AAAA,cAAA;AAAA,YAsBlB,CAAC;AAAA,UAAA;AAAA,QAAA;AAAA,QAEFR,MAAkB,SAAY,OAC7B,gBAAAK,EAAC,QAAA,EAAK,WAAU,4EACb,UAAA;AAAA,UAAAL;AAAA,UAAc;AAAA,UAAEA,MAAkB,IAAI,SAAS;AAAA,QAAA,EAAA,CAClD;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR;"}
@@ -1,5 +1,7 @@
1
1
  export { PlateMapEditor, getPlateMapScopedWellId } from './PlateMapEditor';
2
- export type { PlateMapEditorProps } from './PlateMapEditor';
2
+ export type { PlateMapEditorProps, PlateMapEditorHandle, PlateMapEditorClassNames, PlateMapEditorFormPlacement, PlateMapEditorManifestOptions, PlateMapEditorStackAt, } from './PlateMapEditor';
3
+ export { usePlateMapEditorState, defaultColorForWell, defaultEmptyEntry } from './usePlateMapEditorState';
4
+ export type { UsePlateMapEditorStateOptions, PlateMapEditorState, PlateMapApplyScope, } from './usePlateMapEditorState';
3
5
  export { PlateMapForm } from './PlateMapForm';
4
6
  export type { PlateMapFormProps } from './PlateMapForm';
5
7
  export { PlateMapGrid } from './PlateMapGrid';
@@ -21,11 +23,13 @@ export type { TemplateIOPanelProps } from './TemplateIOPanel';
21
23
  export { plateOptionsFromCsvTriage, triagePlateMapCsvByBarcode, triagePlateMapCsvFile } from './csvPlateTriage';
22
24
  export { ManifestFilterPopover } from './ManifestFilterPopover';
23
25
  export type { ManifestFilterPopoverProps } from './ManifestFilterPopover';
26
+ export { WellQuickPaint } from './WellQuickPaint';
27
+ export type { WellQuickPaintProps } from './WellQuickPaint';
24
28
  export { WellLegend } from './WellLegend';
25
29
  export type { WellLegendItem, WellLegendProps } from './WellLegend';
26
30
  export { PlateZoomControl } from './PlateZoomControl';
27
31
  export type { PlateZoomControlProps } from './PlateZoomControl';
28
32
  export { autoFillPositions, autoFillRecords } from './autoFill';
29
33
  export type { AutoFillOptions, FillStrategy } from './autoFill';
30
- export type { PlateDimensions, PlateMapCsvPlate, PlateMapCsvRow, PlateMapCsvTriage, PlateMapCsvTriageOptions, WellId, WellRecord, WellField, WellFieldKind, WellSelectOption, WellColumn, TemplateOption, ImportExportHandlers, PlateMapGroupOption, PlateMapPlateOption, } from './types';
34
+ export type { PlateDimensions, PlateMapCsvPlate, PlateMapCsvRow, PlateMapCsvTriage, PlateMapCsvTriageOptions, WellId, WellRecord, WellField, WellFieldKind, WellSelectOption, WellColumn, TemplateOption, ImportExportHandlers, PlateMapGroupOption, PlateMapPlateOption, PlateMapEditorLabels, WellManifestTableLabels, } from './types';
31
35
  export { resolveDimensions, rowLabel, parseRowLabel, pos, parsePos, rectPositions, allPositions } from './wellGrid';
@@ -135,3 +135,68 @@ export interface PlateMapGroupOption {
135
135
  borderColor?: string;
136
136
  disabled?: boolean;
137
137
  }
138
+ /**
139
+ * Every user-facing string rendered by `WellManifestTable` (and the filter
140
+ * popover it owns). All optional — an omitted key falls back to the English
141
+ * default shown in its comment.
142
+ */
143
+ export interface WellManifestTableLabels {
144
+ /** "Show all wells" — toggles off the hide-empty filter. */
145
+ showAllWells?: string;
146
+ /** "Hide empty wells" */
147
+ hideEmptyWells?: string;
148
+ /** "Group by" — accessible name of the group-by select. */
149
+ groupBy?: string;
150
+ /** "Group by…" — placeholder of the group-by select. */
151
+ groupByPlaceholder?: string;
152
+ /** "No grouping" */
153
+ noGrouping?: string;
154
+ /** "Selected" — screen-reader name of the selection column. */
155
+ selectedColumn?: string;
156
+ /** "Well" — header of the well-id column. */
157
+ wellColumn?: string;
158
+ /** "No rows. Paint wells on the plate." */
159
+ emptyRows?: React.ReactNode;
160
+ /** "Rows per page" */
161
+ rowsPerPage?: string;
162
+ /** "Prev" */
163
+ previousPage?: string;
164
+ /** "Next" */
165
+ nextPage?: string;
166
+ /** "(blank)" — group key for rows whose grouped field is empty. */
167
+ blankGroup?: string;
168
+ /** "Filter" — trigger for the filter popover. */
169
+ filter?: string;
170
+ /** "Value…" — placeholder for a filter condition's value input. */
171
+ filterValuePlaceholder?: string;
172
+ /** "Clear all" — clears every active filter. */
173
+ clearFilters?: string;
174
+ /** Defaults to `` `${total} rows · ${selected} selected` ``. */
175
+ rowSummary?: (total: number, selected: number) => string;
176
+ /** Defaults to `` `(${count} rows)` ``, singular `row` at 1. */
177
+ groupRowCount?: (count: number) => string;
178
+ /** Defaults to `` `${from}–${to} of ${total}` ``, or `"0 of 0"` when empty. */
179
+ pageRange?: (from: number, to: number, total: number) => string;
180
+ }
181
+ /**
182
+ * Every user-facing string the `PlateMapEditor` shell renders, on top of the
183
+ * manifest's own. All optional; omitted keys fall back to English defaults.
184
+ *
185
+ * Strings that are structural rather than incidental — `plateTitle`,
186
+ * `manifestTitle`, and the import/export menu's labels — stay as their own
187
+ * top-level props, because they are `ReactNode` slots rather than plain text.
188
+ */
189
+ export interface PlateMapEditorLabels extends WellManifestTableLabels {
190
+ /** "Apply" */
191
+ apply?: string;
192
+ /** "Clear wells" */
193
+ clearWells?: string;
194
+ /** "Select wells to edit" — form heading with an empty selection. */
195
+ selectionEmpty?: string;
196
+ /** Defaults to `` `Apply to ${n} wells` ``, singular `well` at 1. */
197
+ selectionCount?: (selectionSize: number) => string;
198
+ /** "Select all" */
199
+ selectAll?: string;
200
+ /** "Deselect all" */
201
+ deselectAll?: string;
202
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const pe=require("react"),Pe=require("./csvPlateTriage.cjs"),ve=require("./PlatePaintGrid.cjs");function be(t){const o=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const n in t)if(n!=="default"){const i=Object.getOwnPropertyDescriptor(t,n);Object.defineProperty(o,n,i.get?i:{enumerable:!0,get:()=>t[n]})}}return o.default=t,Object.freeze(o)}const c=be(pe),ce="::",Ee="plateBarcode";function O(t,o){return`${t}${ce}${o}`}function se(t,o){const n=`${t}${ce}`;return o.startsWith(n)?o.slice(n.length):void 0}function Se(t){return t===void 0?ve.PLATE_MAP_EMPTY_WELL_FILL:"var(--color-chart-1)"}function re(){return{}}function ge(t,o){return{...t??{},...o}}function me(t,o){const n=new Map;return t.forEach(i=>n.set(i.id,i)),(o??[]).forEach(i=>n.set(i.id,i)),[...n.values()]}function Me(t,o){const n=t.find(i=>i.id===o);return n||(o?void 0:t[0])}function ke(t,o){if(!o)return t;const n=new Map;return t.forEach((i,u)=>{const g=se(o,u);g&&n.set(g,i)}),n}function he({importedPlates:t,selectedPlateId:o,isControlled:n,providedPlateCount:i}){const u=t[0]?.id;return u?t.some(g=>g.id===o)?void 0:u:!n&&i===0?null:void 0}function xe({values:t,onChange:o,selection:n,onSelectionChange:i,emptyEntry:u=re,mergeOnApply:g,staged:z,onStagedChange:$,fields:B,cycleFieldOnWellDoubleClick:I,plates:x,activePlateId:K,onPlateChange:k,plateBarcodeField:ae,applyScope:q="active-plate",onImportCsv:w}){const[ie,le]=c.useState({}),[D,V]=c.useState(null),[Y,G]=c.useState(),[H,ue]=c.useState([]),[de,N]=c.useState(),U=z!==void 0,W=z??ie,y=c.useCallback(e=>{U||le(e),$?.(e)},[U,$]),R=g??ge,d=c.useMemo(()=>me(H,x),[H,x]),P=K!==void 0,A=K??de,j=c.useMemo(()=>Me(d,A),[d,A]),T=j?.barcode,l=T,f=!!l,_=ae??Ee,J=c.useRef(l),v=c.useMemo(()=>ke(t,l),[l,t]),Q=c.useCallback(e=>!f||!l?e:O(l,e),[l,f]),X=c.useCallback(e=>{P||N(e),k?.(e)},[P,k]),Z=c.useCallback(async(e,s)=>{if(s){const r=Pe.plateOptionsFromCsvTriage(s);ue(r);const a=he({importedPlates:r,selectedPlateId:A,isControlled:P,providedPlateCount:x?.length??0});a!==void 0&&(P||N(a??void 0),a&&k?.(a))}await w?.(e,s)},[P,w,k,x,A]),m=c.useCallback(e=>!f||!T?e:{...e,[_]:T},[T,_,f]),b=c.useCallback(e=>{if(!f||!l){o(e);return}const s=new Map(t);[...s.keys()].forEach(r=>{se(l,r)&&s.delete(r)}),e.forEach((r,a)=>{s.set(Q(a),m(r))}),o(s)},[l,f,o,m,Q,t]);c.useEffect(()=>{J.current!==l&&(J.current=l,y({}),V(null),G(void 0),n.size>0&&i(new Set))},[l,i,n.size,y]);const p=c.useMemo(()=>{if(!I)return;const e=(B??[]).find(s=>s.key===I);if(!(e?.kind!=="select"||!e.options?.length))return e},[I,B]),C=c.useCallback(e=>e==="all-plates"||e==="active-plate"?e:q,[q]),L=c.useCallback(e=>e==="all-plates"&&f&&d.length>0,[d.length,f]),F=c.useCallback((e,s)=>{if(n.size===0)return;if(L(C(s))){const a=new Map(t);d.forEach(S=>{n.forEach(E=>{const M=O(S.barcode,E),h=a.get(M)??u(E),fe=R(h,e,E);a.set(M,{...fe,[_]:S.barcode})})}),o(a);return}const r=new Map(v);n.forEach(a=>{const E=r.get(a)??u(a),M=m(R(E,e,a));r.set(a,M)}),b(r)},[L,d,_,b,u,R,o,C,v,n,m,t]),ee=c.useCallback(e=>F(W,e),[F,W]),te=c.useCallback(e=>{if(n.size===0)return;if(L(C(e))){const r=new Map(t);d.forEach(a=>{n.forEach(S=>r.delete(O(a.barcode,S)))}),o(r);return}const s=new Map(v);n.forEach(r=>s.delete(r)),b(s)},[L,d,b,o,C,v,n,t]),ne=!P||!!k,oe=c.useCallback(e=>{if(!p?.options?.length)return;const s=new Map(v),r=s.get(e),a=r?.[p.key],S=p.options.findIndex(h=>h.value===a),E=p.options[(S+1)%p.options.length];if(!E)return;const M=r??u(e);s.set(e,m({...M,[p.key]:E.value})),b(s),G(h=>({wellId:e,key:(h?.key??0)+1}))},[b,p,u,v,m]);return c.useMemo(()=>({staged:W,setStaged:y,scopedValues:v,commitScopedValues:b,applyStagedToSelection:ee,applyToSelection:F,clearWells:te,availablePlates:d,activePlate:j,isPlateScoped:f,isPlateSelectionControlled:P,canChangePlate:ne,handlePlateChange:X,handleImportCsv:Z,cycleWellField:p?oe:void 0,flashWell:Y,hoveredWellId:D,setHoveredWellId:V}),[ee,F,d,j,ne,te,b,oe,p,Y,Z,X,D,f,P,v,y,W])}exports.defaultColorForWell=Se;exports.defaultEmptyEntry=re;exports.getPlateMapScopedWellId=O;exports.usePlateMapEditorState=xe;
2
+ //# sourceMappingURL=usePlateMapEditorState.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePlateMapEditorState.cjs","sources":["../../../../src/components/composed/PlateMapEditor/usePlateMapEditorState.ts"],"sourcesContent":["import * as React from \"react\";\n\nimport { plateOptionsFromCsvTriage } from \"./csvPlateTriage\";\nimport { PLATE_MAP_EMPTY_WELL_FILL } from \"./PlatePaintGrid\";\n\nimport type { PlateMapCsvTriage, PlateMapPlateOption, WellField, WellId, WellRecord } from \"./types\";\n\nconst PLATE_WELL_KEY_SEPARATOR = \"::\";\nconst DEFAULT_PLATE_BARCODE_FIELD = \"plateBarcode\";\n\n/** Scopes a well id to a plate barcode, matching the editor's storage key format. */\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\n/** Default `colorForWell` — populated wells read as filled, empty wells as surface. */\nexport function defaultColorForWell(well: unknown): string {\n return well === undefined ? PLATE_MAP_EMPTY_WELL_FILL : \"var(--color-chart-1)\";\n}\n\n/**\n * Default `emptyEntry`, used when the caller supplies none.\n *\n * Only sound for a `T` whose properties are all optional — which is the case it\n * exists for (read-only or single-category views). Supply your own `emptyEntry`\n * when `T` has required fields; this cannot construct them.\n */\nexport function defaultEmptyEntry<T extends WellRecord>(): T {\n return {} as T;\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\n/**\n * Which plates Apply/Clear write to.\n *\n * - `\"active-plate\"` (default) — only the plate currently shown.\n * - `\"all-plates\"` — the same well positions on every plate in `plates`, each\n * row stamped with its own plate's barcode. Falls back to `\"active-plate\"`\n * when the editor isn't plate-scoped.\n */\nexport type PlateMapApplyScope = \"active-plate\" | \"all-plates\";\n\nexport interface UsePlateMapEditorStateOptions<T extends WellRecord = WellRecord> {\n values: Map<WellId, T>;\n onChange: (next: Map<WellId, T>) => void;\n selection: Set<WellId>;\n onSelectionChange: (next: Set<WellId>) => void;\n\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\n /**\n * Controlled staged form record. When provided, the hook stops owning staged\n * state — pair with `onStagedChange` to prefill the form, read what's staged,\n * or drive Apply from outside (bulk paste, \"copy from another well\", …).\n */\n staged?: Partial<T>;\n onStagedChange?: (next: Partial<T>) => void;\n\n /** Field schema, required only for `cycleFieldOnWellDoubleClick`. */\n fields?: WellField<T>[];\n /** Select field cycled when double-clicking a single well, e.g. role painting. */\n cycleFieldOnWellDoubleClick?: keyof T & string;\n\n /** User-provided plates. Barcodes are never generated by the editor. */\n plates?: PlateMapPlateOption[];\n activePlateId?: string;\n onPlateChange?: (plateId: string) => void;\n /** Row field used to stamp the active barcode onto edited wells. Defaults to `plateBarcode`. */\n plateBarcodeField?: keyof T & string;\n /**\n * Default scope for Apply/Clear. Defaults to `\"active-plate\"`.\n * Both functions accept a per-call override, so a host can offer\n * \"Apply\" and \"Apply to all plates\" side by side without changing this.\n */\n applyScope?: PlateMapApplyScope;\n onImportCsv?: (file: File, triage?: PlateMapCsvTriage) => void | Promise<void>;\n}\n\nexport interface PlateMapEditorState<T extends WellRecord = WellRecord> {\n /** Staged form record applied across the selection on `applyStagedToSelection`. */\n staged: Partial<T>;\n setStaged: (next: Partial<T>) => void;\n /** Values narrowed to the active plate. Identical to `values` when unscoped. */\n scopedValues: Map<WellId, T>;\n /** Writes plate-scoped values back through `onChange`, re-scoping keys. */\n commitScopedValues: (next: Map<WellId, T>) => void;\n /**\n * Applies the staged record across the selection. Pass a scope to override\n * `applyScope` for this call.\n *\n * Safe to wire straight to `onClick` — a non-scope argument (such as the\n * click event React passes) is ignored and the default scope is used.\n */\n applyStagedToSelection: (scope?: PlateMapApplyScope) => void;\n /** Clears the selected wells. Same scope-override rules as Apply. */\n clearWells: (scope?: PlateMapApplyScope) => void;\n /**\n * Applies an arbitrary patch across the selection, bypassing the staged\n * record — used for one-click actions like quick-paint.\n */\n applyToSelection: (patch: Partial<T>, scope?: PlateMapApplyScope) => void;\n\n availablePlates: PlateMapPlateOption[];\n activePlate: PlateMapPlateOption | undefined;\n isPlateScoped: boolean;\n isPlateSelectionControlled: boolean;\n canChangePlate: boolean;\n handlePlateChange: (plateId: string) => void;\n handleImportCsv: (file: File, triage?: PlateMapCsvTriage) => Promise<void>;\n\n /** Set when `cycleFieldOnWellDoubleClick` resolves to a usable select field. */\n cycleWellField: ((wellId: WellId) => void) | undefined;\n flashWell: { wellId: WellId; key: number } | undefined;\n hoveredWellId: WellId | null;\n setHoveredWellId: (wellId: WellId | null) => void;\n}\n\n/**\n * The staged-edit controller behind `PlateMapEditor` — staged form record,\n * Apply/Clear across the selection, plate scoping and barcode stamping, CSV\n * plate triage, and double-click field cycling.\n *\n * Use it when you need a layout `PlateMapEditor` can't express and are\n * composing `PlateMapForm` / `PlateMapGrid` / `PlateMapManifest` yourself, so\n * you don't have to re-implement the apply semantics. `PlateMapEditor` is a\n * thin layout over this hook.\n */\nexport function usePlateMapEditorState<T extends WellRecord = WellRecord>({\n values,\n onChange,\n selection,\n onSelectionChange,\n emptyEntry = defaultEmptyEntry,\n mergeOnApply,\n staged: controlledStaged,\n onStagedChange,\n fields,\n cycleFieldOnWellDoubleClick,\n plates,\n activePlateId,\n onPlateChange,\n plateBarcodeField,\n applyScope = \"active-plate\",\n onImportCsv,\n}: UsePlateMapEditorStateOptions<T>): PlateMapEditorState<T> {\n const [internalStaged, setInternalStaged] = React.useState<Partial<T>>({});\n const [hoveredWellId, setHoveredWellId] = 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 isStagedControlled = controlledStaged !== undefined;\n const staged = controlledStaged ?? internalStaged;\n const setStaged = React.useCallback(\n (next: Partial<T>) => {\n if (!isStagedControlled) setInternalStaged(next);\n onStagedChange?.(next);\n },\n [isStagedControlled, onStagedChange],\n );\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 setHoveredWellId(null);\n setFlashWell(undefined);\n if (selection.size > 0) onSelectionChange(new Set());\n }, [activePlateKey, onSelectionChange, selection.size, setStaged]);\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 // `onApply`/`onClear` are wired straight to `onClick` by `WellMetadataForm`,\n // so these receive a MouseEvent as their first argument. Only accept the two\n // literal scopes; anything else means \"no override\".\n const resolveScope = React.useCallback(\n (override?: unknown): PlateMapApplyScope =>\n override === \"all-plates\" || override === \"active-plate\" ? override : applyScope,\n [applyScope],\n );\n\n const appliesToEveryPlate = React.useCallback(\n (scope: PlateMapApplyScope) => scope === \"all-plates\" && isPlateScoped && availablePlates.length > 0,\n [availablePlates.length, isPlateScoped],\n );\n\n /**\n * Applies an arbitrary patch across the selection. `applyStagedToSelection`\n * is this with the staged record; quick-paint uses it with a single field.\n */\n const applyToSelection = React.useCallback(\n (patch: Partial<T>, scope?: PlateMapApplyScope) => {\n if (selection.size === 0) return;\n\n if (appliesToEveryPlate(resolveScope(scope))) {\n const next = new Map(values);\n availablePlates.forEach((plate) => {\n selection.forEach((wellId) => {\n const key = getPlateMapScopedWellId(plate.barcode, wellId);\n const base = next.get(key) ?? emptyEntry(wellId);\n const merged = merge(base, patch, wellId) as Record<string, unknown>;\n next.set(key, { ...merged, [barcodeField]: plate.barcode } as T);\n });\n });\n onChange(next);\n return;\n }\n\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, patch, wellId));\n next.set(wellId, merged);\n });\n commitScopedValues(next);\n },\n [\n appliesToEveryPlate,\n availablePlates,\n barcodeField,\n commitScopedValues,\n emptyEntry,\n merge,\n onChange,\n resolveScope,\n scopedValues,\n selection,\n stampActivePlateBarcode,\n values,\n ],\n );\n\n const applyStagedToSelection = React.useCallback(\n (scope?: PlateMapApplyScope) => applyToSelection(staged, scope),\n [applyToSelection, staged],\n );\n\n const clearWells = React.useCallback(\n (scope?: PlateMapApplyScope) => {\n if (selection.size === 0) return;\n\n if (appliesToEveryPlate(resolveScope(scope))) {\n const next = new Map(values);\n availablePlates.forEach((plate) => {\n selection.forEach((wellId) => next.delete(getPlateMapScopedWellId(plate.barcode, wellId)));\n });\n onChange(next);\n return;\n }\n\n const next = new Map(scopedValues);\n selection.forEach((wellId) => next.delete(wellId));\n commitScopedValues(next);\n },\n [\n appliesToEveryPlate,\n availablePlates,\n commitScopedValues,\n onChange,\n resolveScope,\n scopedValues,\n selection,\n values,\n ],\n );\n\n const canChangePlate = !isPlateSelectionControlled || !!onPlateChange;\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 // Memoised so the object identity is stable across renders: consumers put\n // this in effect and `useImperativeHandle` dependency lists.\n return React.useMemo(\n () => ({\n staged,\n setStaged,\n scopedValues,\n commitScopedValues,\n applyStagedToSelection,\n applyToSelection,\n clearWells,\n availablePlates,\n activePlate,\n isPlateScoped,\n isPlateSelectionControlled,\n canChangePlate,\n handlePlateChange,\n handleImportCsv,\n cycleWellField: doubleClickCycleField ? cycleWellField : undefined,\n flashWell,\n hoveredWellId,\n setHoveredWellId,\n }),\n [\n applyStagedToSelection,\n applyToSelection,\n availablePlates,\n activePlate,\n canChangePlate,\n clearWells,\n commitScopedValues,\n cycleWellField,\n doubleClickCycleField,\n flashWell,\n handleImportCsv,\n handlePlateChange,\n hoveredWellId,\n isPlateScoped,\n isPlateSelectionControlled,\n scopedValues,\n setStaged,\n staged,\n ],\n );\n}\n"],"names":["PLATE_WELL_KEY_SEPARATOR","DEFAULT_PLATE_BARCODE_FIELD","getPlateMapScopedWellId","plateBarcode","wellId","wellIdFromPlateWellKey","plateId","key","prefix","defaultColorForWell","well","PLATE_MAP_EMPTY_WELL_FILL","defaultEmptyEntry","defaultMerge","existing","staged","mergePlateOptions","importedPlates","providedPlates","merged","plate","resolveActivePlate","plates","selectedPlateId","matchingPlate","buildScopedValues","values","plateKey","next","row","resolveImportedPlateSelection","isControlled","providedPlateCount","firstImportedPlateId","usePlateMapEditorState","onChange","selection","onSelectionChange","emptyEntry","mergeOnApply","controlledStaged","onStagedChange","fields","cycleFieldOnWellDoubleClick","activePlateId","onPlateChange","plateBarcodeField","applyScope","onImportCsv","internalStaged","setInternalStaged","React","hoveredWellId","setHoveredWellId","flashWell","setFlashWell","csvPlates","setCsvPlates","internalActivePlateId","setInternalActivePlateId","isStagedControlled","setStaged","merge","availablePlates","isPlateSelectionControlled","activePlate","activePlateBarcode","activePlateKey","isPlateScoped","barcodeField","previousActivePlateKey","scopedValues","toStoredWellKey","handlePlateChange","handleImportCsv","file","triage","nextCsvPlates","plateOptionsFromCsvTriage","nextSelection","stampActivePlateBarcode","commitScopedValues","nextScopedValues","doubleClickCycleField","field","f","resolveScope","override","appliesToEveryPlate","scope","applyToSelection","patch","base","applyStagedToSelection","clearWells","canChangePlate","cycleWellField","currentValue","currentIndex","opt","nextOption","current"],"mappings":"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"}
@@ -0,0 +1,109 @@
1
+ import { PlateMapCsvTriage, PlateMapPlateOption, WellField, WellId, WellRecord } from './types';
2
+ /** Scopes a well id to a plate barcode, matching the editor's storage key format. */
3
+ export declare function getPlateMapScopedWellId(plateBarcode: string, wellId: WellId): WellId;
4
+ /** Default `colorForWell` — populated wells read as filled, empty wells as surface. */
5
+ export declare function defaultColorForWell(well: unknown): string;
6
+ /**
7
+ * Default `emptyEntry`, used when the caller supplies none.
8
+ *
9
+ * Only sound for a `T` whose properties are all optional — which is the case it
10
+ * exists for (read-only or single-category views). Supply your own `emptyEntry`
11
+ * when `T` has required fields; this cannot construct them.
12
+ */
13
+ export declare function defaultEmptyEntry<T extends WellRecord>(): T;
14
+ /**
15
+ * Which plates Apply/Clear write to.
16
+ *
17
+ * - `"active-plate"` (default) — only the plate currently shown.
18
+ * - `"all-plates"` — the same well positions on every plate in `plates`, each
19
+ * row stamped with its own plate's barcode. Falls back to `"active-plate"`
20
+ * when the editor isn't plate-scoped.
21
+ */
22
+ export type PlateMapApplyScope = "active-plate" | "all-plates";
23
+ export interface UsePlateMapEditorStateOptions<T extends WellRecord = WellRecord> {
24
+ values: Map<WellId, T>;
25
+ onChange: (next: Map<WellId, T>) => void;
26
+ selection: Set<WellId>;
27
+ onSelectionChange: (next: Set<WellId>) => void;
28
+ /** Builds an empty record when a well is freshly created. Defaults to `{}`. */
29
+ emptyEntry?: (wellId: WellId) => T;
30
+ /**
31
+ * Merges the staged form record onto an existing well record on Apply.
32
+ * Defaults to a shallow merge (form keys overwrite existing keys when set).
33
+ */
34
+ mergeOnApply?: (existing: T | undefined, staged: Partial<T>, wellId: WellId) => T;
35
+ /**
36
+ * Controlled staged form record. When provided, the hook stops owning staged
37
+ * state — pair with `onStagedChange` to prefill the form, read what's staged,
38
+ * or drive Apply from outside (bulk paste, "copy from another well", …).
39
+ */
40
+ staged?: Partial<T>;
41
+ onStagedChange?: (next: Partial<T>) => void;
42
+ /** Field schema, required only for `cycleFieldOnWellDoubleClick`. */
43
+ fields?: WellField<T>[];
44
+ /** Select field cycled when double-clicking a single well, e.g. role painting. */
45
+ cycleFieldOnWellDoubleClick?: keyof T & string;
46
+ /** User-provided plates. Barcodes are never generated by the editor. */
47
+ plates?: PlateMapPlateOption[];
48
+ activePlateId?: string;
49
+ onPlateChange?: (plateId: string) => void;
50
+ /** Row field used to stamp the active barcode onto edited wells. Defaults to `plateBarcode`. */
51
+ plateBarcodeField?: keyof T & string;
52
+ /**
53
+ * Default scope for Apply/Clear. Defaults to `"active-plate"`.
54
+ * Both functions accept a per-call override, so a host can offer
55
+ * "Apply" and "Apply to all plates" side by side without changing this.
56
+ */
57
+ applyScope?: PlateMapApplyScope;
58
+ onImportCsv?: (file: File, triage?: PlateMapCsvTriage) => void | Promise<void>;
59
+ }
60
+ export interface PlateMapEditorState<T extends WellRecord = WellRecord> {
61
+ /** Staged form record applied across the selection on `applyStagedToSelection`. */
62
+ staged: Partial<T>;
63
+ setStaged: (next: Partial<T>) => void;
64
+ /** Values narrowed to the active plate. Identical to `values` when unscoped. */
65
+ scopedValues: Map<WellId, T>;
66
+ /** Writes plate-scoped values back through `onChange`, re-scoping keys. */
67
+ commitScopedValues: (next: Map<WellId, T>) => void;
68
+ /**
69
+ * Applies the staged record across the selection. Pass a scope to override
70
+ * `applyScope` for this call.
71
+ *
72
+ * Safe to wire straight to `onClick` — a non-scope argument (such as the
73
+ * click event React passes) is ignored and the default scope is used.
74
+ */
75
+ applyStagedToSelection: (scope?: PlateMapApplyScope) => void;
76
+ /** Clears the selected wells. Same scope-override rules as Apply. */
77
+ clearWells: (scope?: PlateMapApplyScope) => void;
78
+ /**
79
+ * Applies an arbitrary patch across the selection, bypassing the staged
80
+ * record — used for one-click actions like quick-paint.
81
+ */
82
+ applyToSelection: (patch: Partial<T>, scope?: PlateMapApplyScope) => void;
83
+ availablePlates: PlateMapPlateOption[];
84
+ activePlate: PlateMapPlateOption | undefined;
85
+ isPlateScoped: boolean;
86
+ isPlateSelectionControlled: boolean;
87
+ canChangePlate: boolean;
88
+ handlePlateChange: (plateId: string) => void;
89
+ handleImportCsv: (file: File, triage?: PlateMapCsvTriage) => Promise<void>;
90
+ /** Set when `cycleFieldOnWellDoubleClick` resolves to a usable select field. */
91
+ cycleWellField: ((wellId: WellId) => void) | undefined;
92
+ flashWell: {
93
+ wellId: WellId;
94
+ key: number;
95
+ } | undefined;
96
+ hoveredWellId: WellId | null;
97
+ setHoveredWellId: (wellId: WellId | null) => void;
98
+ }
99
+ /**
100
+ * The staged-edit controller behind `PlateMapEditor` — staged form record,
101
+ * Apply/Clear across the selection, plate scoping and barcode stamping, CSV
102
+ * plate triage, and double-click field cycling.
103
+ *
104
+ * Use it when you need a layout `PlateMapEditor` can't express and are
105
+ * composing `PlateMapForm` / `PlateMapGrid` / `PlateMapManifest` yourself, so
106
+ * you don't have to re-implement the apply semantics. `PlateMapEditor` is a
107
+ * thin layout over this hook.
108
+ */
109
+ export declare function usePlateMapEditorState<T extends WellRecord = WellRecord>({ values, onChange, selection, onSelectionChange, emptyEntry, mergeOnApply, staged: controlledStaged, onStagedChange, fields, cycleFieldOnWellDoubleClick, plates, activePlateId, onPlateChange, plateBarcodeField, applyScope, onImportCsv, }: UsePlateMapEditorStateOptions<T>): PlateMapEditorState<T>;