@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.156.1 → 1.0.0-beta.158.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +113 -0
  2. package/dist/components/composed/DataAppShell/RightPanel.cjs +1 -1
  3. package/dist/components/composed/DataAppShell/RightPanel.cjs.map +1 -1
  4. package/dist/components/composed/DataAppShell/RightPanel.d.ts +11 -1
  5. package/dist/components/composed/DataAppShell/RightPanel.js +128 -126
  6. package/dist/components/composed/DataAppShell/RightPanel.js.map +1 -1
  7. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +1 -1
  8. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -1
  9. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.d.ts +5 -1
  10. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +32 -30
  11. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -1
  12. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +1 -1
  13. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -1
  14. package/dist/components/composed/PlateMapEditor/PlateMapEditor.d.ts +269 -13
  15. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +392 -310
  16. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -1
  17. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs +1 -1
  18. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs.map +1 -1
  19. package/dist/components/composed/PlateMapEditor/PlateMapForm.d.ts +5 -1
  20. package/dist/components/composed/PlateMapEditor/PlateMapForm.js +19 -15
  21. package/dist/components/composed/PlateMapEditor/PlateMapForm.js.map +1 -1
  22. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs +1 -1
  23. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs.map +1 -1
  24. package/dist/components/composed/PlateMapEditor/PlateMapGrid.d.ts +12 -1
  25. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js +69 -63
  26. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js.map +1 -1
  27. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs +1 -1
  28. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs.map +1 -1
  29. package/dist/components/composed/PlateMapEditor/PlateMapManifest.d.ts +13 -4
  30. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js +38 -29
  31. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js.map +1 -1
  32. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +1 -1
  33. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -1
  34. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.d.ts +6 -1
  35. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +490 -275
  36. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -1
  37. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +1 -1
  38. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -1
  39. package/dist/components/composed/PlateMapEditor/WellLegend.d.ts +8 -2
  40. package/dist/components/composed/PlateMapEditor/WellLegend.js +8 -7
  41. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -1
  42. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +1 -1
  43. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -1
  44. package/dist/components/composed/PlateMapEditor/WellManifestTable.d.ts +6 -2
  45. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +288 -256
  46. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -1
  47. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +1 -1
  48. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -1
  49. package/dist/components/composed/PlateMapEditor/WellMetadataForm.d.ts +5 -1
  50. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +61 -66
  51. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -1
  52. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs +2 -0
  53. package/dist/components/composed/PlateMapEditor/WellQuickPaint.cjs.map +1 -0
  54. package/dist/components/composed/PlateMapEditor/WellQuickPaint.d.ts +23 -0
  55. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js +75 -0
  56. package/dist/components/composed/PlateMapEditor/WellQuickPaint.js.map +1 -0
  57. package/dist/components/composed/PlateMapEditor/index.d.ts +6 -2
  58. package/dist/components/composed/PlateMapEditor/types.d.ts +65 -0
  59. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs +2 -0
  60. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.cjs.map +1 -0
  61. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.d.ts +109 -0
  62. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js +246 -0
  63. package/dist/components/composed/PlateMapEditor/usePlateMapEditorState.js.map +1 -0
  64. package/dist/composed/PlateMapEditor.cjs +1 -1
  65. package/dist/composed/PlateMapEditor.js +48 -42
  66. package/dist/composed/PlateMapEditor.js.map +1 -1
  67. package/dist/index.cjs +1 -1
  68. package/dist/index.css +1 -1
  69. package/dist/index.js +701 -695
  70. package/dist/index.js.map +1 -1
  71. package/dist/index.scoped.css +1 -1
  72. package/dist/index.tailwind.css +1 -1
  73. package/package.json +1 -1
@@ -7,6 +7,10 @@ export interface ManifestFilterPopoverProps {
7
7
  filters: FilterCondition[];
8
8
  onFiltersChange: (next: FilterCondition[]) => void;
9
9
  triggerLabel?: string;
10
+ /** Placeholder for a condition's value input. Defaults to `"Value…"`. */
11
+ valuePlaceholder?: string;
12
+ /** Label for the clear-every-filter button. Defaults to `"Clear all"`. */
13
+ clearAllLabel?: string;
10
14
  className?: string;
11
15
  }
12
- export declare function ManifestFilterPopover({ columns, columnLabel, filters, onFiltersChange, triggerLabel, className, }: ManifestFilterPopoverProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function ManifestFilterPopover({ columns, columnLabel, filters, onFiltersChange, triggerLabel, valuePlaceholder, clearAllLabel, className, }: ManifestFilterPopoverProps): import("react/jsx-runtime").JSX.Element;
@@ -1,11 +1,11 @@
1
1
  import { jsxs as l, jsx as t } from "react/jsx-runtime";
2
- import { ListFilterIcon as P, XIcon as R, PlusIcon as S } from "lucide-react";
2
+ import { ListFilterIcon as S, XIcon as q, PlusIcon as T } from "lucide-react";
3
3
  import { Button as m } from "../../../ui/button.js";
4
- import { Input as q } from "../../../ui/input.js";
5
- import { Popover as T, PopoverTrigger as V, PopoverContent as k } from "../../../ui/popover.js";
4
+ import { Input as V } from "../../../ui/input.js";
5
+ import { Popover as k, PopoverTrigger as U, PopoverContent as j } from "../../../ui/popover.js";
6
6
  import { Select as v, SelectTrigger as g, SelectValue as _, SelectContent as x, SelectItem as y } from "../../../ui/select.js";
7
- import { cn as U } from "../../../lib/utils.js";
8
- const j = {
7
+ import { cn as L } from "../../../lib/utils.js";
8
+ const B = {
9
9
  contains: "contains",
10
10
  equals: "equals",
11
11
  not_equals: "not equals",
@@ -13,7 +13,7 @@ const j = {
13
13
  ends_with: "ends with",
14
14
  is_empty: "is empty",
15
15
  is_not_empty: "is not empty"
16
- }, L = ["is_empty", "is_not_empty"], C = [
16
+ }, D = ["is_empty", "is_not_empty"], C = [
17
17
  "contains",
18
18
  "equals",
19
19
  "not_equals",
@@ -22,51 +22,53 @@ const j = {
22
22
  "is_empty",
23
23
  "is_not_empty"
24
24
  ];
25
- function B(n, u) {
25
+ function $(n, u) {
26
26
  const r = u?.[0] ?? "contains";
27
27
  return { id: crypto.randomUUID(), columnId: n, operator: r, value: "" };
28
28
  }
29
- function Q({
29
+ function Y({
30
30
  columns: n,
31
31
  columnLabel: u,
32
32
  filters: r,
33
33
  onFiltersChange: s,
34
34
  triggerLabel: d = "Filter",
35
- className: I
35
+ valuePlaceholder: I = "Value…",
36
+ clearAllLabel: N = "Clear all",
37
+ className: b
36
38
  }) {
37
- const N = (e) => u?.(e) ?? e, h = n[0], b = h?.columnId ?? "", w = () => s([...r, B(b, h?.operators)]), z = (e) => s(r.filter((c) => c.id !== e)), p = (e, c) => s(r.map((o) => o.id === e ? { ...o, ...c } : o)), F = () => s([]), i = r.length;
38
- return /* @__PURE__ */ l(T, { children: [
39
- /* @__PURE__ */ t(V, { asChild: !0, children: /* @__PURE__ */ l(
39
+ const w = (e) => u?.(e) ?? e, h = n[0], z = h?.columnId ?? "", F = () => s([...r, $(z, h?.operators)]), A = (e) => s(r.filter((c) => c.id !== e)), p = (e, c) => s(r.map((o) => o.id === e ? { ...o, ...c } : o)), E = () => s([]), i = r.length;
40
+ return /* @__PURE__ */ l(k, { children: [
41
+ /* @__PURE__ */ t(U, { asChild: !0, children: /* @__PURE__ */ l(
40
42
  m,
41
43
  {
42
44
  type: "button",
43
45
  variant: "outline",
44
46
  size: "sm",
45
47
  "data-slot": "manifest-filter",
46
- className: U(I),
48
+ className: L(b),
47
49
  "aria-label": i > 0 ? `${d} (${i} active)` : d,
48
50
  children: [
49
- /* @__PURE__ */ t(P, { className: "size-3.5" }),
51
+ /* @__PURE__ */ t(S, { className: "size-3.5" }),
50
52
  d,
51
53
  i > 0 ? /* @__PURE__ */ t("span", { className: "flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground", children: i }) : null
52
54
  ]
53
55
  }
54
56
  ) }),
55
- /* @__PURE__ */ t(k, { align: "end", className: "min-w-80", "aria-label": "Filter conditions", children: /* @__PURE__ */ l("div", { className: "flex flex-col gap-2", children: [
57
+ /* @__PURE__ */ t(j, { align: "end", className: "min-w-80", "aria-label": "Filter conditions", children: /* @__PURE__ */ l("div", { className: "flex flex-col gap-2", children: [
56
58
  r.map((e) => {
57
- const o = n.find((a) => a.columnId === e.columnId)?.operators ?? C, A = L.includes(e.operator);
59
+ const o = n.find((a) => a.columnId === e.columnId)?.operators ?? C, O = D.includes(e.operator);
58
60
  return /* @__PURE__ */ l("div", { className: "flex items-center gap-2", children: [
59
61
  /* @__PURE__ */ l(
60
62
  v,
61
63
  {
62
64
  value: e.columnId,
63
65
  onValueChange: (a) => {
64
- const f = n.find((O) => O.columnId === a)?.operators ?? C, E = f.includes(e.operator) ? e.operator : f[0] ?? "contains";
65
- p(e.id, { columnId: a, operator: E, value: "" });
66
+ const f = n.find((R) => R.columnId === a)?.operators ?? C, P = f.includes(e.operator) ? e.operator : f[0] ?? "contains";
67
+ p(e.id, { columnId: a, operator: P, value: "" });
66
68
  },
67
69
  children: [
68
70
  /* @__PURE__ */ t(g, { "aria-label": "Filter column", size: "sm", className: "w-36", children: /* @__PURE__ */ t(_, {}) }),
69
- /* @__PURE__ */ t(x, { children: n.map((a) => /* @__PURE__ */ t(y, { value: a.columnId, children: a.label ?? N(a.columnId) }, a.columnId)) })
71
+ /* @__PURE__ */ t(x, { children: n.map((a) => /* @__PURE__ */ t(y, { value: a.columnId, children: a.label ?? w(a.columnId) }, a.columnId)) })
70
72
  ]
71
73
  }
72
74
  ),
@@ -77,15 +79,15 @@ function Q({
77
79
  onValueChange: (a) => p(e.id, { operator: a, value: "" }),
78
80
  children: [
79
81
  /* @__PURE__ */ t(g, { "aria-label": "Filter operator", size: "sm", className: "w-32", children: /* @__PURE__ */ t(_, {}) }),
80
- /* @__PURE__ */ t(x, { children: o.map((a) => /* @__PURE__ */ t(y, { value: a, children: j[a] }, a)) })
82
+ /* @__PURE__ */ t(x, { children: o.map((a) => /* @__PURE__ */ t(y, { value: a, children: B[a] }, a)) })
81
83
  ]
82
84
  }
83
85
  ),
84
- A ? /* @__PURE__ */ t("div", { className: "h-8 w-40", "aria-hidden": !0 }) : /* @__PURE__ */ t(
85
- q,
86
+ O ? /* @__PURE__ */ t("div", { className: "h-8 w-40", "aria-hidden": !0 }) : /* @__PURE__ */ t(
87
+ V,
86
88
  {
87
89
  className: "w-40",
88
- placeholder: "Value…",
90
+ placeholder: I,
89
91
  value: e.value,
90
92
  onChange: (a) => p(e.id, { value: a.target.value })
91
93
  }
@@ -97,9 +99,9 @@ function Q({
97
99
  variant: "ghost",
98
100
  size: "icon",
99
101
  className: "size-8 shrink-0 text-muted-foreground hover:text-foreground",
100
- onClick: () => z(e.id),
102
+ onClick: () => A(e.id),
101
103
  "aria-label": "Remove filter",
102
- children: /* @__PURE__ */ t(R, { className: "size-3.5" })
104
+ children: /* @__PURE__ */ t(q, { className: "size-3.5" })
103
105
  }
104
106
  )
105
107
  ] }, e.id);
@@ -111,10 +113,10 @@ function Q({
111
113
  type: "button",
112
114
  variant: "outline",
113
115
  size: "sm",
114
- onClick: w,
116
+ onClick: F,
115
117
  disabled: n.length === 0,
116
118
  children: [
117
- /* @__PURE__ */ t(S, { className: "size-3.5" }),
119
+ /* @__PURE__ */ t(T, { className: "size-3.5" }),
118
120
  "Add filter"
119
121
  ]
120
122
  }
@@ -126,8 +128,8 @@ function Q({
126
128
  variant: "ghost",
127
129
  size: "sm",
128
130
  className: "text-muted-foreground",
129
- onClick: F,
130
- children: "Clear all"
131
+ onClick: E,
132
+ children: N
131
133
  }
132
134
  ) : null
133
135
  ] })
@@ -135,6 +137,6 @@ function Q({
135
137
  ] });
136
138
  }
137
139
  export {
138
- Q as ManifestFilterPopover
140
+ Y as ManifestFilterPopover
139
141
  };
140
142
  //# sourceMappingURL=ManifestFilterPopover.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ManifestFilterPopover.js","sources":["../../../../src/components/composed/PlateMapEditor/ManifestFilterPopover.tsx"],"sourcesContent":["import { ListFilterIcon, PlusIcon, XIcon } from \"lucide-react\";\n\nimport type { FilterColumnConfig, FilterCondition, FilterOperator } from \"@/components/ui/data-table/data-table\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n contains: \"contains\",\n equals: \"equals\",\n not_equals: \"not equals\",\n starts_with: \"starts with\",\n ends_with: \"ends with\",\n is_empty: \"is empty\",\n is_not_empty: \"is not empty\",\n};\n\nconst VALUE_FREE_OPERATORS: FilterOperator[] = [\"is_empty\", \"is_not_empty\"];\n\nconst DEFAULT_OPERATORS: FilterOperator[] = [\n \"contains\",\n \"equals\",\n \"not_equals\",\n \"starts_with\",\n \"ends_with\",\n \"is_empty\",\n \"is_not_empty\",\n];\n\nfunction makeCondition(columnId: string, allowedOperators?: FilterOperator[]): FilterCondition {\n const operator = allowedOperators?.[0] ?? \"contains\";\n return { id: crypto.randomUUID(), columnId, operator, value: \"\" };\n}\n\nexport interface ManifestFilterPopoverProps {\n /** Filterable columns. Operator subsets can be specified per column. */\n columns: FilterColumnConfig[];\n /** Column id → display label resolver. */\n columnLabel?: (columnId: string) => string;\n filters: FilterCondition[];\n onFiltersChange: (next: FilterCondition[]) => void;\n triggerLabel?: string;\n className?: string;\n}\n\nexport function ManifestFilterPopover({\n columns,\n columnLabel,\n filters,\n onFiltersChange,\n triggerLabel = \"Filter\",\n className,\n}: ManifestFilterPopoverProps) {\n const labelFor = (columnId: string): string => columnLabel?.(columnId) ?? columnId;\n const firstColumn = columns[0];\n const firstColumnId = firstColumn?.columnId ?? \"\";\n\n const addFilter = () => onFiltersChange([...filters, makeCondition(firstColumnId, firstColumn?.operators)]);\n const removeFilter = (id: string) => onFiltersChange(filters.filter((cond) => cond.id !== id));\n const updateFilter = (id: string, patch: Partial<FilterCondition>) =>\n onFiltersChange(filters.map((cond) => (cond.id === id ? { ...cond, ...patch } : cond)));\n const clearAll = () => onFiltersChange([]);\n\n const activeCount = filters.length;\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-slot=\"manifest-filter\"\n className={cn(className)}\n aria-label={activeCount > 0 ? `${triggerLabel} (${activeCount} active)` : triggerLabel}\n >\n <ListFilterIcon className=\"size-3.5\" />\n {triggerLabel}\n {activeCount > 0 ? (\n <span className=\"flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground\">\n {activeCount}\n </span>\n ) : null}\n </Button>\n </PopoverTrigger>\n\n <PopoverContent align=\"end\" className=\"min-w-80\" aria-label=\"Filter conditions\">\n <div className=\"flex flex-col gap-2\">\n {filters.map((condition) => {\n const colConfig = columns.find((c) => c.columnId === condition.columnId);\n const operators = colConfig?.operators ?? DEFAULT_OPERATORS;\n const isValueFree = VALUE_FREE_OPERATORS.includes(condition.operator);\n return (\n <div key={condition.id} className=\"flex items-center gap-2\">\n <Select\n value={condition.columnId}\n onValueChange={(value) => {\n const nextConfig = columns.find((c) => c.columnId === value);\n const nextOperators = nextConfig?.operators ?? DEFAULT_OPERATORS;\n const nextOperator = nextOperators.includes(condition.operator)\n ? condition.operator\n : (nextOperators[0] ?? \"contains\");\n updateFilter(condition.id, { columnId: value, operator: nextOperator, value: \"\" });\n }}\n >\n <SelectTrigger aria-label=\"Filter column\" size=\"sm\" className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {columns.map((c) => (\n <SelectItem key={c.columnId} value={c.columnId}>\n {c.label ?? labelFor(c.columnId)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(value) =>\n updateFilter(condition.id, { operator: value as FilterOperator, value: \"\" })\n }\n >\n <SelectTrigger aria-label=\"Filter operator\" size=\"sm\" className=\"w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {isValueFree ? (\n <div className=\"h-8 w-40\" aria-hidden />\n ) : (\n <Input\n className=\"w-40\"\n placeholder=\"Value…\"\n value={condition.value}\n onChange={(event) => updateFilter(condition.id, { value: event.target.value })}\n />\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-8 shrink-0 text-muted-foreground hover:text-foreground\"\n onClick={() => removeFilter(condition.id)}\n aria-label=\"Remove filter\"\n >\n <XIcon className=\"size-3.5\" />\n </Button>\n </div>\n );\n })}\n\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={addFilter}\n disabled={columns.length === 0}\n >\n <PlusIcon className=\"size-3.5\" />\n Add filter\n </Button>\n {filters.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-muted-foreground\"\n onClick={clearAll}\n >\n Clear all\n </Button>\n ) : null}\n </div>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["OPERATOR_LABELS","VALUE_FREE_OPERATORS","DEFAULT_OPERATORS","makeCondition","columnId","allowedOperators","operator","ManifestFilterPopover","columns","columnLabel","filters","onFiltersChange","triggerLabel","className","labelFor","firstColumn","firstColumnId","addFilter","removeFilter","id","cond","updateFilter","patch","clearAll","activeCount","Popover","jsx","PopoverTrigger","jsxs","Button","cn","ListFilterIcon","PopoverContent","condition","operators","c","isValueFree","Select","value","nextOperators","nextOperator","SelectTrigger","SelectValue","SelectContent","SelectItem","op","Input","event","XIcon","PlusIcon"],"mappings":"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"}
1
+ {"version":3,"file":"ManifestFilterPopover.js","sources":["../../../../src/components/composed/PlateMapEditor/ManifestFilterPopover.tsx"],"sourcesContent":["import { ListFilterIcon, PlusIcon, XIcon } from \"lucide-react\";\n\nimport type { FilterColumnConfig, FilterCondition, FilterOperator } from \"@/components/ui/data-table/data-table\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@/components/ui/popover\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\nconst OPERATOR_LABELS: Record<FilterOperator, string> = {\n contains: \"contains\",\n equals: \"equals\",\n not_equals: \"not equals\",\n starts_with: \"starts with\",\n ends_with: \"ends with\",\n is_empty: \"is empty\",\n is_not_empty: \"is not empty\",\n};\n\nconst VALUE_FREE_OPERATORS: FilterOperator[] = [\"is_empty\", \"is_not_empty\"];\n\nconst DEFAULT_OPERATORS: FilterOperator[] = [\n \"contains\",\n \"equals\",\n \"not_equals\",\n \"starts_with\",\n \"ends_with\",\n \"is_empty\",\n \"is_not_empty\",\n];\n\nfunction makeCondition(columnId: string, allowedOperators?: FilterOperator[]): FilterCondition {\n const operator = allowedOperators?.[0] ?? \"contains\";\n return { id: crypto.randomUUID(), columnId, operator, value: \"\" };\n}\n\nexport interface ManifestFilterPopoverProps {\n /** Filterable columns. Operator subsets can be specified per column. */\n columns: FilterColumnConfig[];\n /** Column id → display label resolver. */\n columnLabel?: (columnId: string) => string;\n filters: FilterCondition[];\n onFiltersChange: (next: FilterCondition[]) => void;\n triggerLabel?: string;\n /** Placeholder for a condition's value input. Defaults to `\"Value…\"`. */\n valuePlaceholder?: string;\n /** Label for the clear-every-filter button. Defaults to `\"Clear all\"`. */\n clearAllLabel?: string;\n className?: string;\n}\n\nexport function ManifestFilterPopover({\n columns,\n columnLabel,\n filters,\n onFiltersChange,\n triggerLabel = \"Filter\",\n valuePlaceholder = \"Value…\",\n clearAllLabel = \"Clear all\",\n className,\n}: ManifestFilterPopoverProps) {\n const labelFor = (columnId: string): string => columnLabel?.(columnId) ?? columnId;\n const firstColumn = columns[0];\n const firstColumnId = firstColumn?.columnId ?? \"\";\n\n const addFilter = () => onFiltersChange([...filters, makeCondition(firstColumnId, firstColumn?.operators)]);\n const removeFilter = (id: string) => onFiltersChange(filters.filter((cond) => cond.id !== id));\n const updateFilter = (id: string, patch: Partial<FilterCondition>) =>\n onFiltersChange(filters.map((cond) => (cond.id === id ? { ...cond, ...patch } : cond)));\n const clearAll = () => onFiltersChange([]);\n\n const activeCount = filters.length;\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n data-slot=\"manifest-filter\"\n className={cn(className)}\n aria-label={activeCount > 0 ? `${triggerLabel} (${activeCount} active)` : triggerLabel}\n >\n <ListFilterIcon className=\"size-3.5\" />\n {triggerLabel}\n {activeCount > 0 ? (\n <span className=\"flex size-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground\">\n {activeCount}\n </span>\n ) : null}\n </Button>\n </PopoverTrigger>\n\n <PopoverContent align=\"end\" className=\"min-w-80\" aria-label=\"Filter conditions\">\n <div className=\"flex flex-col gap-2\">\n {filters.map((condition) => {\n const colConfig = columns.find((c) => c.columnId === condition.columnId);\n const operators = colConfig?.operators ?? DEFAULT_OPERATORS;\n const isValueFree = VALUE_FREE_OPERATORS.includes(condition.operator);\n return (\n <div key={condition.id} className=\"flex items-center gap-2\">\n <Select\n value={condition.columnId}\n onValueChange={(value) => {\n const nextConfig = columns.find((c) => c.columnId === value);\n const nextOperators = nextConfig?.operators ?? DEFAULT_OPERATORS;\n const nextOperator = nextOperators.includes(condition.operator)\n ? condition.operator\n : (nextOperators[0] ?? \"contains\");\n updateFilter(condition.id, { columnId: value, operator: nextOperator, value: \"\" });\n }}\n >\n <SelectTrigger aria-label=\"Filter column\" size=\"sm\" className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {columns.map((c) => (\n <SelectItem key={c.columnId} value={c.columnId}>\n {c.label ?? labelFor(c.columnId)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(value) =>\n updateFilter(condition.id, { operator: value as FilterOperator, value: \"\" })\n }\n >\n <SelectTrigger aria-label=\"Filter operator\" size=\"sm\" className=\"w-32\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {isValueFree ? (\n <div className=\"h-8 w-40\" aria-hidden />\n ) : (\n <Input\n className=\"w-40\"\n placeholder={valuePlaceholder}\n value={condition.value}\n onChange={(event) => updateFilter(condition.id, { value: event.target.value })}\n />\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"size-8 shrink-0 text-muted-foreground hover:text-foreground\"\n onClick={() => removeFilter(condition.id)}\n aria-label=\"Remove filter\"\n >\n <XIcon className=\"size-3.5\" />\n </Button>\n </div>\n );\n })}\n\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={addFilter}\n disabled={columns.length === 0}\n >\n <PlusIcon className=\"size-3.5\" />\n Add filter\n </Button>\n {filters.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"text-muted-foreground\"\n onClick={clearAll}\n >\n {clearAllLabel}\n </Button>\n ) : null}\n </div>\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n"],"names":["OPERATOR_LABELS","VALUE_FREE_OPERATORS","DEFAULT_OPERATORS","makeCondition","columnId","allowedOperators","operator","ManifestFilterPopover","columns","columnLabel","filters","onFiltersChange","triggerLabel","valuePlaceholder","clearAllLabel","className","labelFor","firstColumn","firstColumnId","addFilter","removeFilter","id","cond","updateFilter","patch","clearAll","activeCount","Popover","jsx","PopoverTrigger","jsxs","Button","cn","ListFilterIcon","PopoverContent","condition","operators","c","isValueFree","Select","value","nextOperators","nextOperator","SelectTrigger","SelectValue","SelectContent","SelectItem","op","Input","event","XIcon","PlusIcon"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const s=require("react/jsx-runtime"),dt=require("react"),ut=require("./csvPlateTriage.cjs"),ft=require("./PlateMapActionsMenu.cjs"),xt=require("./PlateMapForm.cjs"),pt=require("./PlateMapGrid.cjs"),mt=require("./PlateMapManifest.cjs"),Pt=require("./PlateMapPlateSelector.cjs"),J=require("../../../ui/badge.cjs"),f=require("../../../ui/card.cjs"),ht=require("../../../lib/utils.cjs");function vt(t){const n=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const r in t)if(r!=="default"){const l=Object.getOwnPropertyDescriptor(t,r);Object.defineProperty(n,r,l.get?l:{enumerable:!0,get:()=>t[r]})}}return n.default=t,Object.freeze(n)}const o=vt(dt),Q="::",gt="plateBarcode",jt="Plate Barcode";function X(t,n){return`${t}${Q}${n}`}function Z(t,n){const r=`${t}${Q}`;return n.startsWith(r)?n.slice(r.length):void 0}function Mt(t,n){return{...t??{},...n}}function bt(t,n){const r=new Map;return t.forEach(l=>r.set(l.id,l)),(n??[]).forEach(l=>r.set(l.id,l)),[...r.values()]}function St(t,n){const r=t.find(l=>l.id===n);return r||(n?void 0:t[0])}function Ct(t,n){if(!n)return t;const r=new Map;return t.forEach((l,p)=>{const i=Z(n,p);i&&r.set(i,l)}),r}function Et({importedPlates:t,selectedPlateId:n,isControlled:r,providedPlateCount:l}){const p=t[0]?.id;return p?t.some(i=>i.id===n)?void 0:p:!r&&l===0?null:void 0}function wt(t,n){return t>0||!!n}function At(t,n){return!t||!!n}function yt({title:t,badges:n}){return!t&&!n?null:s.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[t?s.jsx("h2",{className:"text-lg font-semibold",children:t}):s.jsx("span",{}),n?s.jsx("div",{className:"flex flex-wrap gap-2",children:n}):null]})}function Nt({format:t,rows:n,columns:r,values:l,onChange:p,selection:i,onSelectionChange:E,fields:j,tableColumns:M,colorForWell:ee,emptyEntry:w,mergeOnApply:te,isPopulated:ne,cycleFieldOnWellDoubleClick:T,title:se,badges:ae,banner:re,legend:le,formExtras:oe,formSlot:ce,footer:q,plateTitle:ie="Plate",plateToolbar:de,plates:A,activePlateId:z,onPlateChange:b,onAddPlate:B,onRemovePlate:ue,addPlateLabel:fe,removePlateLabel:xe,plateSelectorLabel:pe,plateSelectorVariant:me,plateBarcodeField:Pe,plateBarcodeColumnHeader:F=jt,hidePlateBarcodeColumn:O=!1,groups:he,activeGroupId:ve,onGroupClick:ge,renderHoverSummary:je,cellSize:Me,emptyWellFillColor:be,wellShape:Se,framedPlate:Ce,wrapWell:Ee,highlightedWellIds:we,onHoveredWellChange:Ae,manifestFilterable:ye,manifestGroupable:Ne,autoScaleGrid:Te,minCellSize:We,maxCellSize:_e,className:ke,templates:qe,templateId:ze,onTemplateChange:Be,onClearTemplate:Fe,onImportCsv:W,onExportCsv:Oe,onImportTemplate:Re,onExportTemplate:Le,csvAccept:De,templateAccept:Ie,label:He,align:Ke,side:$e,importTemplateLabel:Ue,exportTemplateLabel:Ve,importCsvLabel:Ge,exportCsvLabel:Ye,clearLabel:Je}){const[R,L]=o.useState({}),[Qe,D]=o.useState(null),[I,H]=o.useState(),[K,Xe]=o.useState([]),[Ze,$]=o.useState(),et=te??Mt,y=o.useMemo(()=>bt(K,A),[K,A]),v=z!==void 0,N=z??Ze,U=o.useMemo(()=>St(y,N),[y,N]),P=U?.barcode,d=P,m=!!d,h=Pe??gt,V=o.useRef(d),g=o.useMemo(()=>Ct(l,d),[d,l]),G=o.useCallback(e=>!m||!d?e:X(d,e),[d,m]),tt=o.useCallback(e=>{v||$(e),b?.(e)},[v,b]),nt=o.useCallback(async(e,a)=>{if(a){const c=ut.plateOptionsFromCsvTriage(a);Xe(c);const u=Et({importedPlates:c,selectedPlateId:N,isControlled:v,providedPlateCount:A?.length??0});u!==void 0&&(v||$(u??void 0),u&&b?.(u))}await W?.(e,a)},[v,W,b,A,N]),S=o.useCallback(e=>!m||!P?e:{...e,[h]:P},[P,h,m]),C=o.useCallback(e=>{if(!m||!d){p(e);return}const a=new Map(l);[...a.keys()].forEach(c=>{Z(d,c)&&a.delete(c)}),e.forEach((c,u)=>{a.set(G(u),S(c))}),p(a)},[d,m,p,S,G,l]);o.useEffect(()=>{V.current!==d&&(V.current=d,L({}),D(null),H(void 0),i.size>0&&E(new Set))},[d,E,i.size]);const x=o.useMemo(()=>{if(!T)return;const e=j.find(a=>a.key===T);if(!(e?.kind!=="select"||!e.options?.length))return e},[T,j]),st=()=>{if(i.size===0)return;const e=new Map(g);i.forEach(a=>{const u=e.get(a)??w(a),_=S(et(u,R,a));e.set(a,_)}),C(e)},at=()=>{if(i.size===0)return;const e=new Map(g);i.forEach(a=>e.delete(a)),C(e)},rt=o.useCallback(e=>{if(!x?.options?.length)return;const a=new Map(g),c=a.get(e),u=c?.[x.key],_=x.options.findIndex(k=>k.value===u),Y=x.options[(_+1)%x.options.length];if(!Y)return;const it=c??w(e);a.set(e,S({...it,[x.key]:Y.value})),C(a),H(k=>({wellId:e,key:(k?.key??0)+1}))},[C,x,w,g,S]),lt=o.useMemo(()=>!m||!P||O||M.some(c=>c.field===h||c.id===h)?M:[{id:h,header:F,minWidth:150,render:({row:c})=>{const u=c[h]??P;return s.jsx(J.Badge,{variant:"outline",children:String(u)})}},...M],[P,h,O,m,F,M]),ot=wt(y.length,B),ct=At(v,b);return s.jsxs("div",{"data-slot":"plate-map-editor",className:ht.cn("flex flex-col gap-4",ke),children:[s.jsx(yt,{title:se,badges:ae}),re,s.jsxs("div",{className:"flex flex-wrap gap-3 md:flex-nowrap",children:[s.jsx(f.Card,{className:"flex w-full max-w-[360px] min-w-[300px] basis-[360px] flex-col",size:"sm",children:s.jsx(f.CardContent,{className:"flex h-full flex-1 flex-col gap-3",children:s.jsx(xt.PlateMapForm,{fields:j,value:R,onChange:L,selectionSize:i.size,onApply:st,onClear:at,extras:oe,legend:le,formSlot:ce})})}),s.jsxs(f.Card,{className:"flex min-w-[360px] flex-1 flex-col",size:"sm",children:[s.jsxs(f.CardHeader,{className:"border-b",children:[s.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[s.jsx(f.CardTitle,{className:"min-w-0",children:ie}),ot?s.jsx(Pt.PlateMapPlateSelector,{plates:y,activePlateId:U?.id,onPlateChange:ct?tt:void 0,onAddPlate:B,onRemovePlate:ue,addPlateLabel:fe,removePlateLabel:xe,label:pe,variant:me}):null]}),s.jsx(f.CardAction,{className:"flex flex-wrap items-center gap-2",children:s.jsx(ft.PlateMapActionsMenu,{templates:qe,templateId:ze,onTemplateChange:Be,onClearTemplate:Fe,hasEntries:l.size>0,onImportCsv:W?nt:void 0,onExportCsv:Oe,onImportTemplate:Re,onExportTemplate:Le,csvAccept:De,templateAccept:Ie,label:He,align:Ke,side:$e,importTemplateLabel:Ue,exportTemplateLabel:Ve,importCsvLabel:Ge,exportCsvLabel:Ye,clearLabel:Je})})]}),s.jsx(f.CardContent,{children:s.jsx(pt.PlateMapGrid,{format:t,rows:n,columns:r,values:g,selection:i,onSelectionChange:E,colorForWell:ee,fields:j,renderHoverSummary:je,hoveredWellId:Qe,onHoveredWellChange:e=>{D(e),Ae?.(e)},toolbar:de,emptyWellFillColor:be,wellShape:Se,framed:Ce,wrapWell:Ee,highlightedWellIds:we,onWellDoubleClick:x?rt:void 0,selectionFillMode:x?"well":"selection",flashWellId:I?.wellId,flashWellKey:I?.key,cellSize:Me,autoScale:Te,minCellSize:We,maxCellSize:_e,groups:he,activeGroupId:ve,onGroupClick:ge})})]})]}),s.jsxs(f.Card,{size:"sm",children:[s.jsx(f.CardHeader,{className:"border-b",children:s.jsx(f.CardTitle,{children:"Sample manifest"})}),s.jsx(f.CardContent,{children:s.jsx(mt.PlateMapManifest,{values:g,onChange:C,columns:lt,fields:j,selection:i,onSelectionChange:E,emptyEntry:w,isPopulated:ne,filterable:ye,groupable:Ne})})]}),q?s.jsx("div",{className:"flex justify-end gap-2 pt-2",children:q}):null]})}exports.PlateBadge=J.Badge;exports.PlateMapEditor=Nt;exports.getPlateMapScopedWellId=X;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),pt=require("react"),ut=require("./PlateMapActionsMenu.cjs"),mt=require("./PlateMapForm.cjs"),ft=require("./PlateMapGrid.cjs"),xt=require("./PlateMapManifest.cjs"),gt=require("./PlateMapPlateSelector.cjs"),D=require("./usePlateMapEditorState.cjs"),ht=require("./WellQuickPaint.cjs"),U=require("../../../ui/badge.cjs"),n=require("../../../ui/card.cjs"),j=require("../../../lib/utils.cjs");function wt(a){const l=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(a){for(const i in a)if(i!=="default"){const c=Object.getOwnPropertyDescriptor(a,i);Object.defineProperty(l,i,c.get?c:{enumerable:!0,get:()=>a[i]})}}return l.default=a,Object.freeze(l)}const h=wt(pt),vt="plateBarcode",jt="Plate Barcode",St="Sample manifest",Pt="360px",bt={sm:"@min-[640px]:flex-row @min-[640px]:items-start",md:"@min-[768px]:flex-row @min-[768px]:items-start",lg:"@min-[1024px]:flex-row @min-[1024px]:items-start",xl:"@min-[1280px]:flex-row @min-[1280px]:items-start",never:""},Ct={sm:"@min-[640px]:w-[var(--plate-map-form-width)] @min-[640px]:shrink-0",md:"@min-[768px]:w-[var(--plate-map-form-width)] @min-[768px]:shrink-0",lg:"@min-[1024px]:w-[var(--plate-map-form-width)] @min-[1024px]:shrink-0",xl:"@min-[1280px]:w-[var(--plate-map-form-width)] @min-[1280px]:shrink-0",never:""};function Mt(a,l){return a>0||!!l}function Et({title:a,badges:l}){return!a&&!l?null:e.jsxs("div",{className:"flex flex-wrap items-center justify-between gap-2",children:[a?e.jsx("h2",{className:"text-lg font-semibold",children:a}):e.jsx("span",{}),l?e.jsx("div",{className:"flex flex-wrap gap-2",children:l}):null]})}function yt({visible:a,...l}){return a?e.jsx(gt.PlateMapPlateSelector,{...l}):null}function Tt({hidden:a,title:l,cardClassName:i,slot:c,manifest:P}){return a?null:e.jsxs(n.Card,{size:"sm","data-plate-map-region":"manifest",className:j.cn("w-full min-w-0",i),children:[l?e.jsx(n.CardHeader,{className:"border-b",children:e.jsx(n.CardTitle,{children:l})}):null,e.jsx(n.CardContent,{className:"min-w-0",children:c??P})]})}function Dt({format:a,rows:l,columns:i,values:c,onChange:P,selection:o,onSelectionChange:b,fields:g,tableColumns:w,colorForWell:Q=D.defaultColorForWell,emptyEntry:N,mergeOnApply:K,isPopulated:Y,cycleFieldOnWellDoubleClick:$,quickPaintField:d,staged:J,onStagedChange:X,title:Z,badges:ee,banner:te,legend:C,legendPlacement:W="form",formExtras:ae,formSlot:le,hideForm:re=!1,formPlacement:S="start",stackAt:_="md",formWidth:M=Pt,footer:A,plateTitle:ne="Plate",plateToolbar:se,plateBanner:oe,plateFooter:I,plates:ie,activePlateId:ce,onPlateChange:de,onAddPlate:L,onRemovePlate:pe,addPlateLabel:ue,removePlateLabel:me,plateSelectorLabel:fe,plateSelectorVariant:xe,plateBarcodeField:z,applyScope:ge,plateBarcodeColumnHeader:O=jt,hidePlateBarcodeColumn:F=!1,groups:he,activeGroupId:we,onGroupClick:ve,renderHoverSummary:je,cellSize:Se,emptyWellFillColor:Pe,wellShape:be,selectionFillMode:Ce,framedPlate:Me,wrapWell:Ee,highlightedWellIds:ye,onHoveredWellChange:Te,hideSelectionControls:De,labels:p,hideManifest:Ne=!1,manifestSlot:We,manifestTitle:_e=St,manifest:E,manifestFilterable:Ae,manifestGroupable:Ie,autoScaleGrid:Le,minCellSize:ze,maxCellSize:Oe,className:Fe,style:ke,classNames:u,ref:Be,templates:qe,templateId:He,onTemplateChange:Re,onClearTemplate:Ge,onImportCsv:k,onExportCsv:Ve,onImportTemplate:Ue,onExportTemplate:Qe,csvAccept:Ke,templateAccept:Ye,label:$e,align:Je,side:Xe,importTemplateLabel:Ze,exportTemplateLabel:et,importCsvLabel:tt,exportCsvLabel:at,clearLabel:lt}){const t=D.usePlateMapEditorState({values:c,onChange:P,selection:o,onSelectionChange:b,emptyEntry:N,mergeOnApply:K,staged:J,onStagedChange:X,fields:g,cycleFieldOnWellDoubleClick:$,plates:ie,activePlateId:ce,onPlateChange:de,plateBarcodeField:z,applyScope:ge,onImportCsv:k});h.useImperativeHandle(Be,()=>({apply:r=>t.applyStagedToSelection(r),clear:r=>t.clearWells(r),setStaged:r=>t.setStaged(r),getStaged:()=>t.staged}),[t]);const m={...E,filterable:Ae??E?.filterable,groupable:Ie??E?.groupable},v=z??vt,y=t.activePlate?.barcode,rt=h.useMemo(()=>!t.isPlateScoped||!y||F||w.some(s=>s.field===v||s.id===v)?w:[{id:v,header:O,minWidth:150,render:({row:s})=>{const x=s[v]??y;return e.jsx(U.Badge,{variant:"outline",children:String(x)})}},...w],[y,v,F,t.isPlateScoped,O,w]),[B,T]=h.useState(!1);h.useEffect(()=>T(!1),[o]),h.useEffect(()=>{if(!d)return;const r=f=>{const s=f.target;s?.closest?.("[data-well]")||s?.closest?.('[data-slot="well-quick-paint"]')||T(!0)};return document.addEventListener("pointerdown",r,!0),()=>document.removeEventListener("pointerdown",r,!0)},[d]);const nt=h.useMemo(()=>{if(!d||o.size===0||B)return;const r=g.find(x=>x.key===d);if(r?.kind!=="select"||!r.options?.length)return;let f,s=!0;for(const x of o){const G=t.scopedValues.get(x)?.[d],V=typeof G=="string"?G:void 0;if(s)f=V,s=!1;else if(f!==V){f=void 0;break}}return e.jsx(ht.WellQuickPaint,{options:r.options,activeValue:f,selectionSize:o.size,onPick:x=>{t.applyToSelection({[d]:x}),T(!0)}})},[g,B,d,o,t]),st=Mt(t.availablePlates.length,L),ot=W==="plate"&&C?e.jsx("div",{className:"mt-3 border-t pt-3",children:C}):void 0,q=S==="top"||S==="bottom",it=S==="start"||S==="top",ct=typeof M=="number"?`${M}px`:M,H=re?null:e.jsx(n.Card,{"data-plate-map-region":"form",className:j.cn("flex w-full min-w-0 flex-col",!q&&Ct[_],u?.formCard),size:"sm",children:e.jsx(n.CardContent,{className:"flex h-full flex-1 flex-col gap-3",children:e.jsx(mt.PlateMapForm,{fields:g,value:t.staged,onChange:t.setStaged,selectionSize:o.size,onApply:t.applyStagedToSelection,onClear:t.clearWells,applyLabel:p?.apply,clearLabel:p?.clearWells,selectionEmptyLabel:p?.selectionEmpty,selectionCountLabel:p?.selectionCount,extras:ae,legend:W==="form"?C:void 0,formSlot:le,className:u?.form})})},"form"),R=e.jsxs(n.Card,{"data-plate-map-region":"plate",className:j.cn("flex w-full min-w-0 flex-1 flex-col",u?.plateCard),size:"sm",children:[e.jsxs(n.CardHeader,{className:"border-b",children:[e.jsxs("div",{className:"flex min-w-0 flex-wrap items-center gap-2",children:[e.jsx(n.CardTitle,{className:"min-w-0",children:ne}),e.jsx(yt,{visible:st,plates:t.availablePlates,activePlateId:t.activePlate?.id,onPlateChange:t.canChangePlate?t.handlePlateChange:void 0,onAddPlate:L,onRemovePlate:pe,addPlateLabel:ue,removePlateLabel:me,label:fe,variant:xe})]}),e.jsx(n.CardAction,{className:"flex flex-wrap items-center gap-2",children:e.jsx(ut.PlateMapActionsMenu,{templates:qe,templateId:He,onTemplateChange:Re,onClearTemplate:Ge,hasEntries:c.size>0,onImportCsv:k?t.handleImportCsv:void 0,onExportCsv:Ve,onImportTemplate:Ue,onExportTemplate:Qe,csvAccept:Ke,templateAccept:Ye,label:$e,align:Je,side:Xe,importTemplateLabel:Ze,exportTemplateLabel:et,importCsvLabel:tt,exportCsvLabel:at,clearLabel:lt})})]}),e.jsx(n.CardContent,{className:"min-w-0",children:e.jsx(ft.PlateMapGrid,{format:a,rows:l,columns:i,values:t.scopedValues,selection:o,onSelectionChange:b,colorForWell:Q,fields:g,renderHoverSummary:je,hoveredWellId:t.hoveredWellId,onHoveredWellChange:r=>{t.setHoveredWellId(r),Te?.(r)},banner:oe,toolbar:se,footer:ot,hideSelectionControls:De,selectAllLabel:p?.selectAll,deselectAllLabel:p?.deselectAll,emptyWellFillColor:Pe,wellShape:be,framed:Me,wrapWell:Ee,quickPaint:nt,highlightedWellIds:ye,onWellDoubleClick:t.cycleWellField,selectionFillMode:Ce,flashWellId:t.flashWell?.wellId,flashWellKey:t.flashWell?.key,cellSize:Se,autoScale:Le,minCellSize:ze,maxCellSize:Oe,groups:he,activeGroupId:we,onGroupClick:ve,className:u?.grid})}),I?e.jsx(n.CardFooter,{className:"flex flex-wrap items-center gap-2 border-t",children:I}):null]},"plate"),dt=it?[H,R]:[R,H];return e.jsxs("div",{"data-slot":"plate-map-editor",className:j.cn("flex w-full min-w-0 flex-col gap-4",Fe),style:ke,children:[e.jsx(Et,{title:Z,badges:ee}),te,e.jsx("div",{"data-slot":"plate-map-editor-container",className:"@container w-full min-w-0",style:{"--plate-map-form-width":ct},children:e.jsx("div",{"data-slot":"plate-map-editor-layout",className:j.cn("flex w-full min-w-0 flex-col gap-3",!q&&bt[_],u?.layout),children:dt})}),e.jsx(Tt,{hidden:Ne,title:_e,cardClassName:u?.manifestCard,slot:We,manifest:e.jsx(xt.PlateMapManifest,{values:t.scopedValues,onChange:t.commitScopedValues,columns:rt,fields:g,selection:o,onSelectionChange:b,emptyEntry:N,isPopulated:Y,filterable:m.filterable,filterColumns:m.filterColumns,groupable:m.groupable,defaultGroupBy:m.defaultGroupBy,pageSize:m.pageSize,pageSizeOptions:m.pageSizeOptions,enableFillDown:m.enableFillDown,labels:p,className:u?.manifest})}),A?e.jsx("div",{className:"flex flex-wrap justify-end gap-2 pt-2",children:A}):null]})}exports.getPlateMapScopedWellId=D.getPlateMapScopedWellId;exports.PlateBadge=U.Badge;exports.PlateMapEditor=Dt;
2
2
  //# sourceMappingURL=PlateMapEditor.cjs.map
@@ -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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"}