@tetrascience-npm/tetrascience-react-ui 0.5.0 → 0.6.0-beta.79.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 (192) hide show
  1. package/README.md +84 -37
  2. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs +2 -0
  3. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.cjs.map +1 -0
  4. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js +140 -0
  5. package/dist/components/composed/PlateMapEditor/ManifestFilterPopover.js.map +1 -0
  6. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.cjs +2 -0
  7. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.cjs.map +1 -0
  8. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.js +126 -0
  9. package/dist/components/composed/PlateMapEditor/PlateMapActionsMenu.js.map +1 -0
  10. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs +2 -0
  11. package/dist/components/composed/PlateMapEditor/PlateMapEditor.cjs.map +1 -0
  12. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js +341 -0
  13. package/dist/components/composed/PlateMapEditor/PlateMapEditor.js.map +1 -0
  14. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs +2 -0
  15. package/dist/components/composed/PlateMapEditor/PlateMapForm.cjs.map +1 -0
  16. package/dist/components/composed/PlateMapEditor/PlateMapForm.js +43 -0
  17. package/dist/components/composed/PlateMapEditor/PlateMapForm.js.map +1 -0
  18. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs +2 -0
  19. package/dist/components/composed/PlateMapEditor/PlateMapGrid.cjs.map +1 -0
  20. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js +154 -0
  21. package/dist/components/composed/PlateMapEditor/PlateMapGrid.js.map +1 -0
  22. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs +2 -0
  23. package/dist/components/composed/PlateMapEditor/PlateMapManifest.cjs.map +1 -0
  24. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js +44 -0
  25. package/dist/components/composed/PlateMapEditor/PlateMapManifest.js.map +1 -0
  26. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.cjs +2 -0
  27. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.cjs.map +1 -0
  28. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.js +136 -0
  29. package/dist/components/composed/PlateMapEditor/PlateMapPlateSelector.js.map +1 -0
  30. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs +2 -0
  31. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.cjs.map +1 -0
  32. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js +389 -0
  33. package/dist/components/composed/PlateMapEditor/PlatePaintGrid.js.map +1 -0
  34. package/dist/components/composed/PlateMapEditor/PlateZoomControl.cjs +2 -0
  35. package/dist/components/composed/PlateMapEditor/PlateZoomControl.cjs.map +1 -0
  36. package/dist/components/composed/PlateMapEditor/PlateZoomControl.js +54 -0
  37. package/dist/components/composed/PlateMapEditor/PlateZoomControl.js.map +1 -0
  38. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.cjs +2 -0
  39. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.cjs.map +1 -0
  40. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.js +96 -0
  41. package/dist/components/composed/PlateMapEditor/TemplateIOPanel.js.map +1 -0
  42. package/dist/components/composed/PlateMapEditor/WellLegend.cjs +2 -0
  43. package/dist/components/composed/PlateMapEditor/WellLegend.cjs.map +1 -0
  44. package/dist/components/composed/PlateMapEditor/WellLegend.js +58 -0
  45. package/dist/components/composed/PlateMapEditor/WellLegend.js.map +1 -0
  46. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs +2 -0
  47. package/dist/components/composed/PlateMapEditor/WellManifestTable.cjs.map +1 -0
  48. package/dist/components/composed/PlateMapEditor/WellManifestTable.js +421 -0
  49. package/dist/components/composed/PlateMapEditor/WellManifestTable.js.map +1 -0
  50. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs +2 -0
  51. package/dist/components/composed/PlateMapEditor/WellMetadataForm.cjs.map +1 -0
  52. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js +177 -0
  53. package/dist/components/composed/PlateMapEditor/WellMetadataForm.js.map +1 -0
  54. package/dist/components/composed/PlateMapEditor/autoFill.cjs +2 -0
  55. package/dist/components/composed/PlateMapEditor/autoFill.cjs.map +1 -0
  56. package/dist/components/composed/PlateMapEditor/autoFill.js +41 -0
  57. package/dist/components/composed/PlateMapEditor/autoFill.js.map +1 -0
  58. package/dist/components/composed/PlateMapEditor/csvPlateTriage.cjs +4 -0
  59. package/dist/components/composed/PlateMapEditor/csvPlateTriage.cjs.map +1 -0
  60. package/dist/components/composed/PlateMapEditor/csvPlateTriage.js +103 -0
  61. package/dist/components/composed/PlateMapEditor/csvPlateTriage.js.map +1 -0
  62. package/dist/components/composed/PlateMapEditor/helpers.cjs +2 -0
  63. package/dist/components/composed/PlateMapEditor/helpers.cjs.map +1 -0
  64. package/dist/components/composed/PlateMapEditor/helpers.js +11 -0
  65. package/dist/components/composed/PlateMapEditor/helpers.js.map +1 -0
  66. package/dist/components/composed/PlateMapEditor/wellGrid.cjs +2 -0
  67. package/dist/components/composed/PlateMapEditor/wellGrid.cjs.map +1 -0
  68. package/dist/components/composed/PlateMapEditor/wellGrid.js +56 -0
  69. package/dist/components/composed/PlateMapEditor/wellGrid.js.map +1 -0
  70. package/dist/components/composed/ProcessFlow/ProcessFlow.cjs +2 -0
  71. package/dist/components/composed/ProcessFlow/ProcessFlow.cjs.map +1 -0
  72. package/dist/components/composed/ProcessFlow/ProcessFlow.js +543 -0
  73. package/dist/components/composed/ProcessFlow/ProcessFlow.js.map +1 -0
  74. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.cjs +2 -0
  75. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.cjs.map +1 -0
  76. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.js +84 -0
  77. package/dist/components/composed/ProcessFlow/ProcessFlow.utils.js.map +1 -0
  78. package/dist/components/ui/accordion.cjs +1 -1
  79. package/dist/components/ui/accordion.cjs.map +1 -1
  80. package/dist/components/ui/accordion.js +1 -1
  81. package/dist/components/ui/accordion.js.map +1 -1
  82. package/dist/components/ui/badge.cjs +1 -1
  83. package/dist/components/ui/badge.cjs.map +1 -1
  84. package/dist/components/ui/badge.js +18 -18
  85. package/dist/components/ui/badge.js.map +1 -1
  86. package/dist/components/ui/button.cjs +1 -1
  87. package/dist/components/ui/button.cjs.map +1 -1
  88. package/dist/components/ui/button.js +16 -16
  89. package/dist/components/ui/button.js.map +1 -1
  90. package/dist/components/ui/calendar.cjs +1 -1
  91. package/dist/components/ui/calendar.cjs.map +1 -1
  92. package/dist/components/ui/calendar.js +5 -5
  93. package/dist/components/ui/calendar.js.map +1 -1
  94. package/dist/components/ui/card.cjs +1 -1
  95. package/dist/components/ui/card.cjs.map +1 -1
  96. package/dist/components/ui/card.js +1 -1
  97. package/dist/components/ui/card.js.map +1 -1
  98. package/dist/components/ui/checkbox.cjs +1 -1
  99. package/dist/components/ui/checkbox.cjs.map +1 -1
  100. package/dist/components/ui/checkbox.js +9 -9
  101. package/dist/components/ui/checkbox.js.map +1 -1
  102. package/dist/components/ui/combobox.cjs +1 -1
  103. package/dist/components/ui/combobox.cjs.map +1 -1
  104. package/dist/components/ui/combobox.js +5 -5
  105. package/dist/components/ui/combobox.js.map +1 -1
  106. package/dist/components/ui/data-table/data-table-group.cjs +2 -0
  107. package/dist/components/ui/data-table/data-table-group.cjs.map +1 -0
  108. package/dist/components/ui/data-table/data-table-group.js +118 -0
  109. package/dist/components/ui/data-table/data-table-group.js.map +1 -0
  110. package/dist/components/ui/data-table/data-table-pagination.cjs +1 -1
  111. package/dist/components/ui/data-table/data-table-pagination.cjs.map +1 -1
  112. package/dist/components/ui/data-table/data-table-pagination.js +22 -22
  113. package/dist/components/ui/data-table/data-table-pagination.js.map +1 -1
  114. package/dist/components/ui/data-table/data-table.cjs +1 -1
  115. package/dist/components/ui/data-table/data-table.cjs.map +1 -1
  116. package/dist/components/ui/data-table/data-table.js +567 -316
  117. package/dist/components/ui/data-table/data-table.js.map +1 -1
  118. package/dist/components/ui/dialog.cjs +1 -1
  119. package/dist/components/ui/dialog.cjs.map +1 -1
  120. package/dist/components/ui/dialog.js +13 -13
  121. package/dist/components/ui/dialog.js.map +1 -1
  122. package/dist/components/ui/input-group.cjs +1 -1
  123. package/dist/components/ui/input-group.cjs.map +1 -1
  124. package/dist/components/ui/input-group.js +29 -29
  125. package/dist/components/ui/input-group.js.map +1 -1
  126. package/dist/components/ui/input-otp.cjs +1 -1
  127. package/dist/components/ui/input-otp.cjs.map +1 -1
  128. package/dist/components/ui/input-otp.js +10 -10
  129. package/dist/components/ui/input-otp.js.map +1 -1
  130. package/dist/components/ui/input.cjs +1 -1
  131. package/dist/components/ui/input.cjs.map +1 -1
  132. package/dist/components/ui/input.js +7 -7
  133. package/dist/components/ui/input.js.map +1 -1
  134. package/dist/components/ui/item.cjs +1 -1
  135. package/dist/components/ui/item.cjs.map +1 -1
  136. package/dist/components/ui/item.js +17 -17
  137. package/dist/components/ui/item.js.map +1 -1
  138. package/dist/components/ui/navigation-menu.cjs +1 -1
  139. package/dist/components/ui/navigation-menu.cjs.map +1 -1
  140. package/dist/components/ui/navigation-menu.js +24 -24
  141. package/dist/components/ui/navigation-menu.js.map +1 -1
  142. package/dist/components/ui/popover.cjs +2 -0
  143. package/dist/components/ui/popover.cjs.map +1 -0
  144. package/dist/components/ui/popover.js +45 -0
  145. package/dist/components/ui/popover.js.map +1 -0
  146. package/dist/components/ui/radio-group.cjs +1 -1
  147. package/dist/components/ui/radio-group.cjs.map +1 -1
  148. package/dist/components/ui/radio-group.js +16 -16
  149. package/dist/components/ui/radio-group.js.map +1 -1
  150. package/dist/components/ui/scroll-area.cjs +1 -1
  151. package/dist/components/ui/scroll-area.cjs.map +1 -1
  152. package/dist/components/ui/scroll-area.js +6 -6
  153. package/dist/components/ui/scroll-area.js.map +1 -1
  154. package/dist/components/ui/select.cjs +1 -1
  155. package/dist/components/ui/select.cjs.map +1 -1
  156. package/dist/components/ui/select.js +48 -48
  157. package/dist/components/ui/select.js.map +1 -1
  158. package/dist/components/ui/slider.cjs +1 -1
  159. package/dist/components/ui/slider.cjs.map +1 -1
  160. package/dist/components/ui/slider.js +22 -22
  161. package/dist/components/ui/slider.js.map +1 -1
  162. package/dist/components/ui/switch.cjs +1 -1
  163. package/dist/components/ui/switch.cjs.map +1 -1
  164. package/dist/components/ui/switch.js +14 -14
  165. package/dist/components/ui/switch.js.map +1 -1
  166. package/dist/components/ui/table.cjs +1 -1
  167. package/dist/components/ui/table.cjs.map +1 -1
  168. package/dist/components/ui/table.js +2 -2
  169. package/dist/components/ui/table.js.map +1 -1
  170. package/dist/components/ui/tabs.cjs +1 -1
  171. package/dist/components/ui/tabs.cjs.map +1 -1
  172. package/dist/components/ui/tabs.js +9 -9
  173. package/dist/components/ui/tabs.js.map +1 -1
  174. package/dist/components/ui/textarea.cjs +1 -1
  175. package/dist/components/ui/textarea.cjs.map +1 -1
  176. package/dist/components/ui/textarea.js +6 -6
  177. package/dist/components/ui/textarea.js.map +1 -1
  178. package/dist/components/ui/toggle.cjs +1 -1
  179. package/dist/components/ui/toggle.cjs.map +1 -1
  180. package/dist/components/ui/toggle.js +13 -13
  181. package/dist/components/ui/toggle.js.map +1 -1
  182. package/dist/index.cjs +1 -1
  183. package/dist/index.css +1 -1
  184. package/dist/index.d.ts +830 -3
  185. package/dist/index.js +649 -593
  186. package/dist/index.js.map +1 -1
  187. package/dist/index.tailwind.css +1 -1
  188. package/dist/utils/colors.cjs +1 -1
  189. package/dist/utils/colors.cjs.map +1 -1
  190. package/dist/utils/colors.js +43 -21
  191. package/dist/utils/colors.js.map +1 -1
  192. package/package.json +1 -1
@@ -0,0 +1,389 @@
1
+ import { jsx as g, jsxs as U, Fragment as we } from "react/jsx-runtime";
2
+ import * as E from "react";
3
+ import { rectPositions as te, pos as $, resolveDimensions as ye, rowLabel as _e } from "./wellGrid.js";
4
+ import { parsePos as ze } from "./wellGrid.js";
5
+ import { cn as oe } from "../../../lib/utils.js";
6
+ const be = 34, Ae = 24, Me = 72, ke = 36, n = 26, z = 12, Fe = 1, le = 16, Te = 9, xe = 5, N = 1, Z = 4, se = 4, Re = 3, ne = 5, Y = 650, De = "var(--surface-container)", Oe = "var(--border)";
7
+ function Se(i, l) {
8
+ const r = [];
9
+ for (let o = 0; o < i; o++)
10
+ r.push(
11
+ /* @__PURE__ */ g(
12
+ "text",
13
+ {
14
+ x: n + o * l + l / 2,
15
+ y: n / 2,
16
+ textAnchor: "middle",
17
+ fontSize: le,
18
+ className: "fill-muted-foreground",
19
+ children: o + 1
20
+ },
21
+ `c${o}`
22
+ )
23
+ );
24
+ return r;
25
+ }
26
+ function Ce(i, l) {
27
+ const r = [];
28
+ for (let o = 0; o < i; o++)
29
+ r.push(
30
+ /* @__PURE__ */ g(
31
+ "text",
32
+ {
33
+ x: n - Te,
34
+ y: n + o * l + l / 2 + xe,
35
+ textAnchor: "end",
36
+ fontSize: le,
37
+ className: "fill-muted-foreground",
38
+ children: _e(o)
39
+ },
40
+ `r${o}`
41
+ )
42
+ );
43
+ return r;
44
+ }
45
+ function We(i, l, r) {
46
+ const {
47
+ dims: o,
48
+ cellSize: e,
49
+ values: v,
50
+ selection: k,
51
+ dragPositions: L,
52
+ colorForWell: u,
53
+ emptyWellFillColor: f,
54
+ selectedFillColor: w,
55
+ selectedFillOpacity: _,
56
+ selectionFillMode: T,
57
+ wellShape: p
58
+ } = i, h = $(l, r, o.columns), b = v.get(h), y = k.has(h) || L.has(h), D = b === void 0 && f !== null ? f : u(b, h), F = y && !(y && T === "well" && b !== void 0), x = F ? w : D, R = F ? _ : void 0;
59
+ if (p === "circle") {
60
+ const O = n + r * e + e / 2, A = n + l * e + e / 2, S = e / 2 - N;
61
+ return /* @__PURE__ */ g(
62
+ "circle",
63
+ {
64
+ cx: O,
65
+ cy: A,
66
+ r: S,
67
+ fill: x,
68
+ fillOpacity: R,
69
+ stroke: "none",
70
+ "data-well": h,
71
+ "data-selected": y ? "true" : void 0
72
+ },
73
+ h
74
+ );
75
+ }
76
+ return /* @__PURE__ */ g(
77
+ "rect",
78
+ {
79
+ x: n + r * e,
80
+ y: n + l * e,
81
+ width: e,
82
+ height: e,
83
+ fill: x,
84
+ fillOpacity: R,
85
+ stroke: "none",
86
+ "data-well": h,
87
+ "data-selected": y ? "true" : void 0
88
+ },
89
+ h
90
+ );
91
+ }
92
+ function Pe({ dims: i, ...l }) {
93
+ const r = [];
94
+ for (let o = 0; o < i.rows; o++)
95
+ for (let e = 0; e < i.columns; e++)
96
+ r.push(We({ dims: i, ...l }, o, e));
97
+ return r;
98
+ }
99
+ function Ne(i, l, r) {
100
+ const o = [], e = n, v = n, k = n + i.columns * l, L = n + i.rows * l;
101
+ for (let u = 0; u <= i.columns; u++) {
102
+ const f = n + u * l;
103
+ o.push(
104
+ /* @__PURE__ */ g(
105
+ "line",
106
+ {
107
+ x1: f,
108
+ y1: v,
109
+ x2: f,
110
+ y2: L,
111
+ stroke: r,
112
+ strokeWidth: Z,
113
+ vectorEffect: "non-scaling-stroke",
114
+ shapeRendering: "crispEdges",
115
+ pointerEvents: "none",
116
+ "data-plate-grid-line": `column-${u}`
117
+ },
118
+ `grid-col-${u}`
119
+ )
120
+ );
121
+ }
122
+ for (let u = 0; u <= i.rows; u++) {
123
+ const f = n + u * l;
124
+ o.push(
125
+ /* @__PURE__ */ g(
126
+ "line",
127
+ {
128
+ x1: e,
129
+ y1: f,
130
+ x2: k,
131
+ y2: f,
132
+ stroke: r,
133
+ strokeWidth: Z,
134
+ vectorEffect: "non-scaling-stroke",
135
+ shapeRendering: "crispEdges",
136
+ pointerEvents: "none",
137
+ "data-plate-grid-line": `row-${u}`
138
+ },
139
+ `grid-row-${u}`
140
+ )
141
+ );
142
+ }
143
+ return o;
144
+ }
145
+ function $e(i, l, r) {
146
+ const {
147
+ dims: o,
148
+ cellSize: e,
149
+ selection: v,
150
+ dragPositions: k,
151
+ selectedBorderColor: L,
152
+ flashWellId: u,
153
+ flashWellKey: f,
154
+ wellShape: w,
155
+ highlightedWellIds: _,
156
+ highlightBorderColor: T
157
+ } = i, p = $(l, r, o.columns), h = v.has(p) || k.has(p), b = u === p, y = !h && _.has(p);
158
+ if (!h && !b && !y) return null;
159
+ const D = n + r * e + N, I = n + l * e + N, F = e - N * 2, x = n + r * e + e / 2, R = n + l * e + e / 2, O = e / 2 - N, A = w === "circle", S = A ? { cx: x, cy: R, r: O } : { x: D, y: I, width: F, height: F }, M = (s, C, W) => {
160
+ const a = { ...S, ...s, pointerEvents: "none" };
161
+ return A ? /* @__PURE__ */ g("circle", { ...a, children: C }, W) : /* @__PURE__ */ g("rect", { ...a, children: C }, W);
162
+ }, B = /* @__PURE__ */ U(we, { children: [
163
+ /* @__PURE__ */ g("animate", { attributeName: "fill-opacity", values: "0.24;0.1;0", dur: `${Y}ms`, fill: "freeze" }),
164
+ /* @__PURE__ */ g("animate", { attributeName: "stroke-opacity", values: "0.92;0.42;0", dur: `${Y}ms`, fill: "freeze" }),
165
+ /* @__PURE__ */ g(
166
+ "animate",
167
+ {
168
+ attributeName: "stroke-width",
169
+ values: `${ne};${se}`,
170
+ dur: `${Y}ms`,
171
+ fill: "freeze"
172
+ }
173
+ )
174
+ ] });
175
+ return /* @__PURE__ */ U("g", { children: [
176
+ h ? M({
177
+ fill: "none",
178
+ stroke: L,
179
+ strokeWidth: se,
180
+ "data-well-selection": p
181
+ }) : null,
182
+ y ? M({
183
+ fill: "none",
184
+ stroke: T,
185
+ strokeWidth: Re,
186
+ "data-well-highlight": p
187
+ }) : null,
188
+ b ? M(
189
+ {
190
+ fill: L,
191
+ fillOpacity: 0.24,
192
+ stroke: L,
193
+ strokeOpacity: 0.92,
194
+ strokeWidth: ne,
195
+ "data-well-flash": p
196
+ },
197
+ B,
198
+ `${p}-${f}`
199
+ ) : null
200
+ ] }, `overlay-${p}`);
201
+ }
202
+ function Ie({ dims: i, ...l }) {
203
+ const r = [];
204
+ for (let o = 0; o < i.rows; o++)
205
+ for (let e = 0; e < i.columns; e++) {
206
+ const v = $e({ dims: i, ...l }, o, e);
207
+ v && r.push(v);
208
+ }
209
+ return r;
210
+ }
211
+ function Xe({
212
+ format: i,
213
+ rows: l,
214
+ columns: r,
215
+ values: o,
216
+ selection: e,
217
+ onSelectionChange: v,
218
+ colorForWell: k,
219
+ emptyWellFillColor: L = De,
220
+ wellShape: u = "rect",
221
+ framed: f = !1,
222
+ cellSize: w,
223
+ autoScale: _ = !0,
224
+ minCellSize: T = Ae,
225
+ maxCellSize: p,
226
+ borderColor: h = Oe,
227
+ selectedBorderColor: b = "var(--color-primary)",
228
+ selectedFillColor: y = "var(--color-primary)",
229
+ selectedFillOpacity: D = 0.18,
230
+ selectionFillMode: I = "selection",
231
+ flashWellId: F,
232
+ flashWellKey: x,
233
+ highlightedWellIds: R,
234
+ highlightBorderColor: O = "var(--color-primary)",
235
+ onWellHover: A,
236
+ onWellDoubleClick: S,
237
+ wrapWell: M,
238
+ className: B
239
+ }) {
240
+ const s = ye(i, l, r), C = E.useRef(null), W = E.useRef(null), [a, H] = E.useState(null), [K, re] = E.useState();
241
+ E.useLayoutEffect(() => {
242
+ const c = C.current;
243
+ if (!c || !_ || w !== void 0) return;
244
+ const t = () => re(c.clientWidth);
245
+ if (t(), typeof ResizeObserver > "u") return;
246
+ const d = new ResizeObserver(t);
247
+ return d.observe(c), () => d.disconnect();
248
+ }, [_, w]);
249
+ const m = E.useMemo(() => {
250
+ if (!_ || w !== void 0 || !K)
251
+ return w ?? be;
252
+ const c = p ?? (s.columns > 12 ? ke : Me), t = f ? (z + Fe) * 2 : 0, d = Math.floor((K - n - t) / s.columns);
253
+ return Math.max(T, Math.min(c, d));
254
+ }, [_, w, K, s.columns, f, p, T]), X = E.useCallback(
255
+ (c) => {
256
+ const t = W.current;
257
+ if (!t) return null;
258
+ const d = t.getBoundingClientRect(), P = c.clientX - d.left - n, Le = c.clientY - d.top - n, G = Math.floor(P / m), j = Math.floor(Le / m);
259
+ return j < 0 || j >= s.rows || G < 0 || G >= s.columns ? null : { r: j, c: G };
260
+ },
261
+ [m, s.rows, s.columns]
262
+ ), ce = (c) => {
263
+ const t = X(c);
264
+ if (!t) return;
265
+ const d = c.shiftKey ? "add" : c.altKey ? "remove" : "replace";
266
+ H({ start: t, cur: t, mode: d });
267
+ }, ie = (c) => {
268
+ const t = X(c);
269
+ t && A && A($(t.r, t.c, s.columns)), !(!a || !t) && H({ ...a, cur: t });
270
+ }, q = E.useCallback(() => {
271
+ if (!a) return;
272
+ const c = te(a.start.r, a.start.c, a.cur.r, a.cur.c, s.columns);
273
+ let t;
274
+ a.mode === "replace" ? t = new Set(c) : a.mode === "add" ? (t = new Set(e), c.forEach((d) => t.add(d))) : (t = new Set(e), c.forEach((d) => t.delete(d))), v(t), H(null);
275
+ }, [a, s.columns, v, e]), ae = () => q(), ue = () => {
276
+ q(), A?.(null);
277
+ }, de = (c) => {
278
+ const t = X(c);
279
+ t && S?.($(t.r, t.c, s.columns));
280
+ }, fe = E.useMemo(() => a ? new Set(te(a.start.r, a.start.c, a.cur.r, a.cur.c, s.columns)) : /* @__PURE__ */ new Set(), [a, s.columns]), J = Z, Q = s.columns * m + n + J, V = s.rows * m + n + J, he = Se(s.columns, m), me = Ce(s.rows, m), ee = {
281
+ dims: s,
282
+ cellSize: m,
283
+ values: o,
284
+ selection: e,
285
+ dragPositions: fe,
286
+ colorForWell: k,
287
+ emptyWellFillColor: L,
288
+ borderColor: h,
289
+ selectedBorderColor: b,
290
+ selectedFillColor: y,
291
+ selectedFillOpacity: D,
292
+ selectionFillMode: I,
293
+ wellShape: u,
294
+ highlightedWellIds: R ?? /* @__PURE__ */ new Set(),
295
+ highlightBorderColor: O,
296
+ flashWellId: F,
297
+ flashWellKey: x
298
+ }, pe = Pe(ee), ge = u === "circle" ? [] : Ne(s, m, h), ve = Ie(ee), Ee = E.useMemo(() => {
299
+ if (!M) return [];
300
+ const c = [];
301
+ for (let t = 0; t < s.rows; t++)
302
+ for (let d = 0; d < s.columns; d++) {
303
+ const P = $(t, d, s.columns);
304
+ c.push(
305
+ /* @__PURE__ */ g(
306
+ "div",
307
+ {
308
+ style: {
309
+ position: "absolute",
310
+ left: n + d * m,
311
+ top: n + t * m,
312
+ width: m,
313
+ height: m
314
+ },
315
+ "data-well-id": P,
316
+ children: M(P, m)
317
+ },
318
+ P
319
+ )
320
+ );
321
+ }
322
+ return c;
323
+ }, [M, s.rows, s.columns, m]);
324
+ return /* @__PURE__ */ g(
325
+ "div",
326
+ {
327
+ ref: C,
328
+ className: oe("relative w-full select-none", B),
329
+ "data-slot": "plate-paint-grid",
330
+ children: /* @__PURE__ */ U(
331
+ "div",
332
+ {
333
+ className: oe(
334
+ "relative inline-block",
335
+ f && "rounded-xl border bg-card p-3 shadow-sm"
336
+ ),
337
+ "data-slot": "plate-paint-grid-frame",
338
+ children: [
339
+ /* @__PURE__ */ U(
340
+ "svg",
341
+ {
342
+ ref: W,
343
+ width: Q,
344
+ height: V,
345
+ className: "block cursor-crosshair",
346
+ onMouseDown: ce,
347
+ onMouseMove: ie,
348
+ onMouseUp: ae,
349
+ onMouseLeave: ue,
350
+ onDoubleClick: de,
351
+ role: "group",
352
+ "aria-label": `${s.rows} row by ${s.columns} column plate map. Drag to select wells.`,
353
+ children: [
354
+ he,
355
+ me,
356
+ pe,
357
+ ge,
358
+ ve
359
+ ]
360
+ }
361
+ ),
362
+ M ? /* @__PURE__ */ g(
363
+ "div",
364
+ {
365
+ className: "pointer-events-none absolute",
366
+ style: {
367
+ top: f ? z : 0,
368
+ left: f ? z : 0,
369
+ width: Q,
370
+ height: V
371
+ },
372
+ "aria-hidden": !0,
373
+ "data-slot": "plate-well-overlay",
374
+ children: Ee
375
+ }
376
+ ) : null
377
+ ]
378
+ }
379
+ )
380
+ }
381
+ );
382
+ }
383
+ export {
384
+ Oe as PLATE_MAP_CELL_BORDER,
385
+ De as PLATE_MAP_EMPTY_WELL_FILL,
386
+ Xe as PlatePaintGrid,
387
+ ze as parsePos
388
+ };
389
+ //# sourceMappingURL=PlatePaintGrid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlatePaintGrid.js","sources":["../../../../src/components/composed/PlateMapEditor/PlatePaintGrid.tsx"],"sourcesContent":["import * as React from \"react\";\n\nimport { parsePos, pos, rectPositions, resolveDimensions, rowLabel } from \"./wellGrid\";\n\nimport type { PlateDimensions, PlateFormat, WellId, WellRecord } from \"./types\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst DEFAULT_CELL = 34;\nconst DEFAULT_MIN_AUTO_CELL = 24;\nconst DEFAULT_MAX_AUTO_CELL = 72;\nconst DEFAULT_MAX_DENSE_AUTO_CELL = 36;\nconst LABEL_PAD = 26;\nconst FRAME_PADDING_PX = 12;\nconst FRAME_BORDER_PX = 1;\nconst LABEL_FONT_SIZE = 16;\nconst LABEL_TEXT_INSET = 9;\nconst LABEL_BASELINE_OFFSET = 5;\nconst WELL_INSET = 1;\nconst STROKE_DEFAULT = 4;\nconst STROKE_SELECTED = 4;\nconst STROKE_HIGHLIGHT = 3;\nconst STROKE_FLASH = 5;\nconst FLASH_DURATION_MS = 650;\nexport const PLATE_MAP_EMPTY_WELL_FILL = \"var(--surface-container)\";\nexport const PLATE_MAP_CELL_BORDER = \"var(--border)\";\n\nexport type WellShape = \"rect\" | \"circle\";\n\nexport interface PlatePaintGridProps<T extends WellRecord = WellRecord> {\n format: PlateFormat;\n rows?: number;\n columns?: number;\n values: Map<WellId, T>;\n selection: Set<WellId>;\n onSelectionChange: (next: Set<WellId>) => void;\n /** Returns the fill color for a given well record (or undefined if empty). */\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n /** Fill color for empty wells. Pass `null` to delegate empty wells to `colorForWell`. */\n emptyWellFillColor?: string | null;\n /** Geometric shape of each well. `\"circle\"` matches scientific plate visuals. Defaults to `\"rect\"`. */\n wellShape?: WellShape;\n /**\n * When true, wraps the grid in a card-like surface (rounded, bordered,\n * padded, soft shadow) so it reads as a physical plate. Pairs well with\n * `wellShape=\"circle\"`.\n */\n framed?: boolean;\n /** Pixel size of each well cell. Defaults to 34 when fixed. */\n cellSize?: number;\n /** Resize wells to fill available width when `cellSize` is not fixed. */\n autoScale?: boolean;\n minCellSize?: number;\n /** Defaults to 72 for 96-well style plates and 36 for denser plates. */\n maxCellSize?: number;\n /** Stroke color for non-selected wells. Defaults to a light border. */\n borderColor?: string;\n /** Stroke color for selected wells. Defaults to the kit primary blue. */\n selectedBorderColor?: string;\n /** Fill color for selected wells. Defaults to the kit primary blue. */\n selectedFillColor?: string;\n /** Selected fill opacity. */\n selectedFillOpacity?: number;\n /** Whether selected wells use the selection fill or keep their assigned well color. */\n selectionFillMode?: \"selection\" | \"well\";\n /** Well id that should briefly flash, usually after a double-click assignment. */\n flashWellId?: WellId;\n /** Changing this value restarts the flash animation for the same well. */\n flashWellKey?: number;\n /**\n * Well ids that should render with a highlight ring. Used for cross-component\n * hover sync (e.g. hovering a legend item to highlight matching wells).\n */\n highlightedWellIds?: ReadonlySet<WellId>;\n /** Stroke color for highlighted wells. Defaults to the kit primary blue. */\n highlightBorderColor?: string;\n onWellHover?: (wellId: WellId | null) => void;\n onWellDoubleClick?: (wellId: WellId) => void;\n /**\n * Optional render-prop invoked once per well. The returned node is placed\n * inside an absolutely-positioned cell on top of the SVG, sized to\n * `cellSize`. The wrapper layer is `pointer-events: none` so the SVG keeps\n * pointer interaction by default; consumers wiring drop targets are expected\n * to set `pointer-events: auto` on their own element while a drag is active.\n */\n wrapWell?: (wellId: WellId, cellSize: number) => React.ReactNode;\n className?: string;\n}\n\ntype DragMode = \"replace\" | \"add\" | \"remove\";\n\ninterface DragState {\n start: { r: number; c: number };\n cur: { r: number; c: number };\n mode: DragMode;\n}\n\nfunction buildColumnLabels(columns: number, cellSize: number): React.ReactNode[] {\n const labels: React.ReactNode[] = [];\n for (let c = 0; c < columns; c++) {\n labels.push(\n <text\n key={`c${c}`}\n x={LABEL_PAD + c * cellSize + cellSize / 2}\n y={LABEL_PAD / 2}\n textAnchor=\"middle\"\n fontSize={LABEL_FONT_SIZE}\n className=\"fill-muted-foreground\"\n >\n {c + 1}\n </text>,\n );\n }\n return labels;\n}\n\nfunction buildRowLabels(rows: number, cellSize: number): React.ReactNode[] {\n const labels: React.ReactNode[] = [];\n for (let r = 0; r < rows; r++) {\n labels.push(\n <text\n key={`r${r}`}\n x={LABEL_PAD - LABEL_TEXT_INSET}\n y={LABEL_PAD + r * cellSize + cellSize / 2 + LABEL_BASELINE_OFFSET}\n textAnchor=\"end\"\n fontSize={LABEL_FONT_SIZE}\n className=\"fill-muted-foreground\"\n >\n {rowLabel(r)}\n </text>,\n );\n }\n return labels;\n}\n\ninterface BuildWellCellsArgs<T extends WellRecord> {\n dims: PlateDimensions;\n cellSize: number;\n values: Map<WellId, T>;\n selection: Set<WellId>;\n dragPositions: Set<WellId>;\n colorForWell: (well: T | undefined, wellId: WellId) => string;\n emptyWellFillColor: string | null;\n borderColor: string;\n selectedBorderColor: string;\n selectedFillColor: string;\n selectedFillOpacity: number;\n selectionFillMode: \"selection\" | \"well\";\n wellShape: WellShape;\n highlightedWellIds: ReadonlySet<WellId>;\n highlightBorderColor: string;\n flashWellId?: WellId;\n flashWellKey?: number;\n}\n\nfunction buildWellCell<T extends WellRecord>(\n args: BuildWellCellsArgs<T>,\n row: number,\n column: number,\n): React.ReactNode {\n const {\n dims,\n cellSize,\n values,\n selection,\n dragPositions,\n colorForWell,\n emptyWellFillColor,\n selectedFillColor,\n selectedFillOpacity,\n selectionFillMode,\n wellShape,\n } = args;\n const id = pos(row, column, dims.columns);\n const entry = values.get(id);\n const isSelected = selection.has(id) || dragPositions.has(id);\n const wellFill = entry === undefined && emptyWellFillColor !== null ? emptyWellFillColor : colorForWell(entry, id);\n const usesWellFill = isSelected && selectionFillMode === \"well\" && entry !== undefined;\n const usesSelectionFill = isSelected && !usesWellFill;\n const fill = usesSelectionFill ? selectedFillColor : wellFill;\n const fillOpacity = usesSelectionFill ? selectedFillOpacity : undefined;\n\n if (wellShape === \"circle\") {\n const cx = LABEL_PAD + column * cellSize + cellSize / 2;\n const cy = LABEL_PAD + row * cellSize + cellSize / 2;\n const r = cellSize / 2 - WELL_INSET;\n return (\n <circle\n key={id}\n cx={cx}\n cy={cy}\n r={r}\n fill={fill}\n fillOpacity={fillOpacity}\n stroke=\"none\"\n data-well={id}\n data-selected={isSelected ? \"true\" : undefined}\n />\n );\n }\n\n return (\n <rect\n key={id}\n x={LABEL_PAD + column * cellSize}\n y={LABEL_PAD + row * cellSize}\n width={cellSize}\n height={cellSize}\n fill={fill}\n fillOpacity={fillOpacity}\n stroke=\"none\"\n data-well={id}\n data-selected={isSelected ? \"true\" : undefined}\n />\n );\n}\n\nfunction buildWellCells<T extends WellRecord>({ dims, ...args }: BuildWellCellsArgs<T>): React.ReactNode[] {\n const cells: React.ReactNode[] = [];\n for (let r = 0; r < dims.rows; r++) {\n for (let c = 0; c < dims.columns; c++) {\n cells.push(buildWellCell({ dims, ...args }, r, c));\n }\n }\n return cells;\n}\n\nfunction buildGridLines(dims: PlateDimensions, cellSize: number, borderColor: string): React.ReactNode[] {\n const lines: React.ReactNode[] = [];\n const left = LABEL_PAD;\n const top = LABEL_PAD;\n const right = LABEL_PAD + dims.columns * cellSize;\n const bottom = LABEL_PAD + dims.rows * cellSize;\n\n for (let c = 0; c <= dims.columns; c++) {\n const x = LABEL_PAD + c * cellSize;\n lines.push(\n <line\n key={`grid-col-${c}`}\n x1={x}\n y1={top}\n x2={x}\n y2={bottom}\n stroke={borderColor}\n strokeWidth={STROKE_DEFAULT}\n vectorEffect=\"non-scaling-stroke\"\n shapeRendering=\"crispEdges\"\n pointerEvents=\"none\"\n data-plate-grid-line={`column-${c}`}\n />,\n );\n }\n\n for (let r = 0; r <= dims.rows; r++) {\n const y = LABEL_PAD + r * cellSize;\n lines.push(\n <line\n key={`grid-row-${r}`}\n x1={left}\n y1={y}\n x2={right}\n y2={y}\n stroke={borderColor}\n strokeWidth={STROKE_DEFAULT}\n vectorEffect=\"non-scaling-stroke\"\n shapeRendering=\"crispEdges\"\n pointerEvents=\"none\"\n data-plate-grid-line={`row-${r}`}\n />,\n );\n }\n\n return lines;\n}\n\nfunction buildWellOverlay<T extends WellRecord>(\n args: BuildWellCellsArgs<T>,\n row: number,\n column: number,\n): React.ReactNode {\n const {\n dims,\n cellSize,\n selection,\n dragPositions,\n selectedBorderColor,\n flashWellId,\n flashWellKey,\n wellShape,\n highlightedWellIds,\n highlightBorderColor,\n } = args;\n const id = pos(row, column, dims.columns);\n const isSelected = selection.has(id) || dragPositions.has(id);\n const isFlashing = flashWellId === id;\n const isHighlighted = !isSelected && highlightedWellIds.has(id);\n\n if (!isSelected && !isFlashing && !isHighlighted) return null;\n\n const x = LABEL_PAD + column * cellSize + WELL_INSET;\n const y = LABEL_PAD + row * cellSize + WELL_INSET;\n const size = cellSize - WELL_INSET * 2;\n const cx = LABEL_PAD + column * cellSize + cellSize / 2;\n const cy = LABEL_PAD + row * cellSize + cellSize / 2;\n const r = cellSize / 2 - WELL_INSET;\n const isCircle = wellShape === \"circle\";\n\n type WellShapeProps = Record<string, string | number | undefined>;\n const shapeProps: WellShapeProps = isCircle\n ? { cx, cy, r }\n : { x, y, width: size, height: size };\n\n const renderShape = (\n extraProps: Record<string, string | number | undefined>,\n children?: React.ReactNode,\n elementKey?: string | number,\n ): React.ReactNode => {\n const props = { ...shapeProps, ...extraProps, pointerEvents: \"none\" as const };\n return isCircle ? (\n <circle key={elementKey} {...props}>\n {children}\n </circle>\n ) : (\n <rect key={elementKey} {...props}>\n {children}\n </rect>\n );\n };\n\n const flashAnimations = (\n <>\n <animate attributeName=\"fill-opacity\" values=\"0.24;0.1;0\" dur={`${FLASH_DURATION_MS}ms`} fill=\"freeze\" />\n <animate attributeName=\"stroke-opacity\" values=\"0.92;0.42;0\" dur={`${FLASH_DURATION_MS}ms`} fill=\"freeze\" />\n <animate\n attributeName=\"stroke-width\"\n values={`${STROKE_FLASH};${STROKE_SELECTED}`}\n dur={`${FLASH_DURATION_MS}ms`}\n fill=\"freeze\"\n />\n </>\n );\n\n return (\n <g key={`overlay-${id}`}>\n {isSelected\n ? renderShape({\n fill: \"none\",\n stroke: selectedBorderColor,\n strokeWidth: STROKE_SELECTED,\n \"data-well-selection\": id,\n })\n : null}\n {isHighlighted\n ? renderShape({\n fill: \"none\",\n stroke: highlightBorderColor,\n strokeWidth: STROKE_HIGHLIGHT,\n \"data-well-highlight\": id,\n })\n : null}\n {isFlashing\n ? renderShape(\n {\n fill: selectedBorderColor,\n fillOpacity: 0.24,\n stroke: selectedBorderColor,\n strokeOpacity: 0.92,\n strokeWidth: STROKE_FLASH,\n \"data-well-flash\": id,\n },\n flashAnimations,\n `${id}-${flashWellKey}`,\n )\n : null}\n </g>\n );\n}\n\nfunction buildWellOverlays<T extends WellRecord>({ dims, ...args }: BuildWellCellsArgs<T>): React.ReactNode[] {\n const overlays: React.ReactNode[] = [];\n for (let r = 0; r < dims.rows; r++) {\n for (let c = 0; c < dims.columns; c++) {\n const overlay = buildWellOverlay({ dims, ...args }, r, c);\n if (overlay) overlays.push(overlay);\n }\n }\n return overlays;\n}\n\n/**\n * Interactive plate grid with drag-rectangle selection.\n * - Click & drag: replace selection\n * - Shift + drag: add to selection\n * - Alt + drag: remove from selection\n */\nexport function PlatePaintGrid<T extends WellRecord = WellRecord>({\n format,\n rows,\n columns,\n values,\n selection,\n onSelectionChange,\n colorForWell,\n emptyWellFillColor = PLATE_MAP_EMPTY_WELL_FILL,\n wellShape = \"rect\",\n framed = false,\n cellSize,\n autoScale = true,\n minCellSize = DEFAULT_MIN_AUTO_CELL,\n maxCellSize,\n borderColor = PLATE_MAP_CELL_BORDER,\n selectedBorderColor = \"var(--color-primary)\",\n selectedFillColor = \"var(--color-primary)\",\n selectedFillOpacity = 0.18,\n selectionFillMode = \"selection\",\n flashWellId,\n flashWellKey,\n highlightedWellIds,\n highlightBorderColor = \"var(--color-primary)\",\n onWellHover,\n onWellDoubleClick,\n wrapWell,\n className,\n}: PlatePaintGridProps<T>) {\n const dims = resolveDimensions(format, rows, columns);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const svgRef = React.useRef<SVGSVGElement>(null);\n const [drag, setDrag] = React.useState<DragState | null>(null);\n const [containerWidth, setContainerWidth] = React.useState<number>();\n\n React.useLayoutEffect(() => {\n const node = containerRef.current;\n if (!node || !autoScale || cellSize !== undefined) return;\n\n const update = () => setContainerWidth(node.clientWidth);\n update();\n\n if (typeof ResizeObserver === \"undefined\") return;\n\n const observer = new ResizeObserver(update);\n observer.observe(node);\n return () => observer.disconnect();\n }, [autoScale, cellSize]);\n\n const resolvedCellSize = React.useMemo(() => {\n if (!autoScale || cellSize !== undefined || !containerWidth) {\n return cellSize ?? DEFAULT_CELL;\n }\n const autoMaxCellSize = maxCellSize ?? (dims.columns > 12 ? DEFAULT_MAX_DENSE_AUTO_CELL : DEFAULT_MAX_AUTO_CELL);\n const frameAdjust = framed ? (FRAME_PADDING_PX + FRAME_BORDER_PX) * 2 : 0;\n const fitSize = Math.floor((containerWidth - LABEL_PAD - frameAdjust) / dims.columns);\n return Math.max(minCellSize, Math.min(autoMaxCellSize, fitSize));\n }, [autoScale, cellSize, containerWidth, dims.columns, framed, maxCellSize, minCellSize]);\n\n const cellAt = React.useCallback(\n (evt: React.MouseEvent): { r: number; c: number } | null => {\n const svg = svgRef.current;\n if (!svg) return null;\n const rect = svg.getBoundingClientRect();\n const x = evt.clientX - rect.left - LABEL_PAD;\n const y = evt.clientY - rect.top - LABEL_PAD;\n const c = Math.floor(x / resolvedCellSize);\n const r = Math.floor(y / resolvedCellSize);\n if (r < 0 || r >= dims.rows || c < 0 || c >= dims.columns) return null;\n return { r, c };\n },\n [resolvedCellSize, dims.rows, dims.columns],\n );\n\n const handleDown = (e: React.MouseEvent) => {\n const cell = cellAt(e);\n if (!cell) return;\n const mode: DragMode = e.shiftKey ? \"add\" : e.altKey ? \"remove\" : \"replace\";\n setDrag({ start: cell, cur: cell, mode });\n };\n\n const handleMove = (e: React.MouseEvent) => {\n const cell = cellAt(e);\n if (cell && onWellHover) {\n onWellHover(pos(cell.r, cell.c, dims.columns));\n }\n if (!drag || !cell) return;\n setDrag({ ...drag, cur: cell });\n };\n\n const commitDrag = React.useCallback(() => {\n if (!drag) return;\n const positions = rectPositions(drag.start.r, drag.start.c, drag.cur.r, drag.cur.c, dims.columns);\n let next: Set<WellId>;\n if (drag.mode === \"replace\") {\n next = new Set(positions);\n } else if (drag.mode === \"add\") {\n next = new Set(selection);\n positions.forEach((p) => next.add(p));\n } else {\n next = new Set(selection);\n positions.forEach((p) => next.delete(p));\n }\n onSelectionChange(next);\n setDrag(null);\n }, [drag, dims.columns, onSelectionChange, selection]);\n\n const handleUp = () => commitDrag();\n const handleLeave = () => {\n commitDrag();\n onWellHover?.(null);\n };\n const handleDoubleClick = (e: React.MouseEvent) => {\n const cell = cellAt(e);\n if (!cell) return;\n onWellDoubleClick?.(pos(cell.r, cell.c, dims.columns));\n };\n\n const dragPositions = React.useMemo(() => {\n if (!drag) return new Set<WellId>();\n return new Set(rectPositions(drag.start.r, drag.start.c, drag.cur.r, drag.cur.c, dims.columns));\n }, [drag, dims.columns]);\n\n // Right and bottom edge strokes sit on the plate boundary; give the SVG room so they are not clipped.\n const edgeStrokePadding = STROKE_DEFAULT;\n const width = dims.columns * resolvedCellSize + LABEL_PAD + edgeStrokePadding;\n const height = dims.rows * resolvedCellSize + LABEL_PAD + edgeStrokePadding;\n\n const colLabels = buildColumnLabels(dims.columns, resolvedCellSize);\n const rowLabels = buildRowLabels(dims.rows, resolvedCellSize);\n const resolvedHighlightedWellIds: ReadonlySet<WellId> = highlightedWellIds ?? new Set();\n const wellRenderArgs = {\n dims,\n cellSize: resolvedCellSize,\n values,\n selection,\n dragPositions,\n colorForWell,\n emptyWellFillColor,\n borderColor,\n selectedBorderColor,\n selectedFillColor,\n selectedFillOpacity,\n selectionFillMode,\n wellShape,\n highlightedWellIds: resolvedHighlightedWellIds,\n highlightBorderColor,\n flashWellId,\n flashWellKey,\n };\n const wellCells = buildWellCells(wellRenderArgs);\n const gridLines = wellShape === \"circle\" ? [] : buildGridLines(dims, resolvedCellSize, borderColor);\n const wellOverlays = buildWellOverlays(wellRenderArgs);\n\n const overlayCells = React.useMemo<React.ReactNode[]>(() => {\n if (!wrapWell) return [];\n const cells: React.ReactNode[] = [];\n for (let r = 0; r < dims.rows; r++) {\n for (let c = 0; c < dims.columns; c++) {\n const id = pos(r, c, dims.columns);\n cells.push(\n <div\n key={id}\n style={{\n position: \"absolute\",\n left: LABEL_PAD + c * resolvedCellSize,\n top: LABEL_PAD + r * resolvedCellSize,\n width: resolvedCellSize,\n height: resolvedCellSize,\n }}\n data-well-id={id}\n >\n {wrapWell(id, resolvedCellSize)}\n </div>,\n );\n }\n }\n return cells;\n }, [wrapWell, dims.rows, dims.columns, resolvedCellSize]);\n\n return (\n <div\n ref={containerRef}\n className={cn(\"relative w-full select-none\", className)}\n data-slot=\"plate-paint-grid\"\n >\n <div\n className={cn(\n \"relative inline-block\",\n framed && \"rounded-xl border bg-card p-3 shadow-sm\",\n )}\n data-slot=\"plate-paint-grid-frame\"\n >\n <svg\n ref={svgRef}\n width={width}\n height={height}\n className=\"block cursor-crosshair\"\n onMouseDown={handleDown}\n onMouseMove={handleMove}\n onMouseUp={handleUp}\n onMouseLeave={handleLeave}\n onDoubleClick={handleDoubleClick}\n role=\"group\"\n aria-label={`${dims.rows} row by ${dims.columns} column plate map. Drag to select wells.`}\n >\n {colLabels}\n {rowLabels}\n {wellCells}\n {gridLines}\n {wellOverlays}\n </svg>\n {wrapWell ? (\n <div\n className=\"pointer-events-none absolute\"\n style={{\n top: framed ? FRAME_PADDING_PX : 0,\n left: framed ? FRAME_PADDING_PX : 0,\n width,\n height,\n }}\n aria-hidden\n data-slot=\"plate-well-overlay\"\n >\n {overlayCells}\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport { parsePos };\n"],"names":["DEFAULT_CELL","DEFAULT_MIN_AUTO_CELL","DEFAULT_MAX_AUTO_CELL","DEFAULT_MAX_DENSE_AUTO_CELL","LABEL_PAD","FRAME_PADDING_PX","FRAME_BORDER_PX","LABEL_FONT_SIZE","LABEL_TEXT_INSET","LABEL_BASELINE_OFFSET","WELL_INSET","STROKE_DEFAULT","STROKE_SELECTED","STROKE_HIGHLIGHT","STROKE_FLASH","FLASH_DURATION_MS","PLATE_MAP_EMPTY_WELL_FILL","PLATE_MAP_CELL_BORDER","buildColumnLabels","columns","cellSize","labels","c","jsx","buildRowLabels","rows","r","buildWellCell","args","row","column","dims","values","selection","dragPositions","colorForWell","emptyWellFillColor","selectedFillColor","selectedFillOpacity","selectionFillMode","wellShape","id","pos","entry","isSelected","wellFill","usesSelectionFill","fill","fillOpacity","cx","cy","buildWellCells","cells","buildGridLines","borderColor","lines","left","top","right","bottom","x","y","buildWellOverlay","selectedBorderColor","flashWellId","flashWellKey","highlightedWellIds","highlightBorderColor","isFlashing","isHighlighted","size","isCircle","shapeProps","renderShape","extraProps","children","elementKey","props","flashAnimations","jsxs","Fragment","buildWellOverlays","overlays","overlay","PlatePaintGrid","format","onSelectionChange","framed","autoScale","minCellSize","maxCellSize","onWellHover","onWellDoubleClick","wrapWell","className","resolveDimensions","containerRef","React","svgRef","drag","setDrag","containerWidth","setContainerWidth","node","update","observer","resolvedCellSize","autoMaxCellSize","frameAdjust","fitSize","cellAt","evt","svg","rect","handleDown","e","cell","mode","handleMove","commitDrag","positions","rectPositions","next","p","handleUp","handleLeave","handleDoubleClick","edgeStrokePadding","width","height","colLabels","rowLabels","wellRenderArgs","wellCells","gridLines","wellOverlays","overlayCells","cn"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),a=require("lucide-react"),s=require("../../ui/button.cjs"),l=require("../../ui/button-group.cjs"),b=require("../../../lib/utils.cjs"),x=2;function h(t,o,n){return Math.min(n,Math.max(o,t))}function j({zoom:t,onZoomChange:o,step:n=.1,min:r=.5,max:i=2,showReadout:c=!0,className:d}){const u=m=>o(h(Number(m.toFixed(x)),r,i));return e.jsxs(l.ButtonGroup,{className:b.cn(d),"data-slot":"plate-zoom-control",children:[e.jsx(s.Button,{type:"button",variant:"outline",size:"icon-sm","aria-label":"Zoom out",disabled:t<=r,onClick:()=>u(t-n),children:e.jsx(a.ZoomOut,{"aria-hidden":!0})}),c?e.jsxs(l.ButtonGroupText,{className:"min-w-[3.25rem] justify-center text-xs tabular-nums",children:[Math.round(t*100),"%"]}):null,e.jsx(s.Button,{type:"button",variant:"outline",size:"icon-sm","aria-label":"Zoom in",disabled:t>=i,onClick:()=>u(t+n),children:e.jsx(a.ZoomIn,{"aria-hidden":!0})})]})}exports.PlateZoomControl=j;
2
+ //# sourceMappingURL=PlateZoomControl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlateZoomControl.cjs","sources":["../../../../src/components/composed/PlateMapEditor/PlateZoomControl.tsx"],"sourcesContent":["import { ZoomIn, ZoomOut } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ButtonGroup, ButtonGroupText } from \"@/components/ui/button-group\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateZoomControlProps {\n zoom: number;\n onZoomChange: (next: number) => void;\n /** Increment per click. Defaults to 0.1. */\n step?: number;\n min?: number;\n max?: number;\n /** Render a percentage readout between the buttons. Defaults to true. */\n showReadout?: boolean;\n className?: string;\n}\n\nconst ZOOM_DECIMALS = 2;\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nexport function PlateZoomControl({\n zoom,\n onZoomChange,\n step = 0.1,\n min = 0.5,\n max = 2,\n showReadout = true,\n className,\n}: PlateZoomControlProps) {\n const setZoom = (next: number) => onZoomChange(clamp(Number(next.toFixed(ZOOM_DECIMALS)), min, max));\n\n return (\n <ButtonGroup className={cn(className)} data-slot=\"plate-zoom-control\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom out\"\n disabled={zoom <= min}\n onClick={() => setZoom(zoom - step)}\n >\n <ZoomOut aria-hidden />\n </Button>\n {showReadout ? (\n <ButtonGroupText className=\"min-w-[3.25rem] justify-center text-xs tabular-nums\">\n {Math.round(zoom * 100)}%\n </ButtonGroupText>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom in\"\n disabled={zoom >= max}\n onClick={() => setZoom(zoom + step)}\n >\n <ZoomIn aria-hidden />\n </Button>\n </ButtonGroup>\n );\n}\n"],"names":["ZOOM_DECIMALS","clamp","value","min","max","PlateZoomControl","zoom","onZoomChange","step","showReadout","className","setZoom","next","ButtonGroup","cn","jsx","Button","ZoomOut","jsxs","ButtonGroupText","ZoomIn"],"mappings":"2PAkBMA,EAAgB,EAEtB,SAASC,EAAMC,EAAeC,EAAaC,EAAqB,CAC9D,OAAO,KAAK,IAAIA,EAAK,KAAK,IAAID,EAAKD,CAAK,CAAC,CAC3C,CAEO,SAASG,EAAiB,CAC/B,KAAAC,EACA,aAAAC,EACA,KAAAC,EAAO,GACP,IAAAL,EAAM,GACN,IAAAC,EAAM,EACN,YAAAK,EAAc,GACd,UAAAC,CACF,EAA0B,CACxB,MAAMC,EAAWC,GAAiBL,EAAaN,EAAM,OAAOW,EAAK,QAAQZ,CAAa,CAAC,EAAGG,EAAKC,CAAG,CAAC,EAEnG,cACGS,cAAA,CAAY,UAAWC,EAAAA,GAAGJ,CAAS,EAAG,YAAU,qBAC/C,SAAA,CAAAK,EAAAA,IAACC,EAAAA,OAAA,CACC,KAAK,SACL,QAAQ,UACR,KAAK,UACL,aAAW,WACX,SAAUV,GAAQH,EAClB,QAAS,IAAMQ,EAAQL,EAAOE,CAAI,EAElC,SAAAO,EAAAA,IAACE,EAAAA,QAAA,CAAQ,cAAW,EAAA,CAAC,CAAA,CAAA,EAEtBR,EACCS,EAAAA,KAACC,EAAAA,gBAAA,CAAgB,UAAU,sDACxB,SAAA,CAAA,KAAK,MAAMb,EAAO,GAAG,EAAE,GAAA,CAAA,CAC1B,EACE,KACJS,EAAAA,IAACC,EAAAA,OAAA,CACC,KAAK,SACL,QAAQ,UACR,KAAK,UACL,aAAW,UACX,SAAUV,GAAQF,EAClB,QAAS,IAAMO,EAAQL,EAAOE,CAAI,EAElC,SAAAO,EAAAA,IAACK,EAAAA,OAAA,CAAO,cAAW,EAAA,CAAC,CAAA,CAAA,CACtB,EACF,CAEJ"}
@@ -0,0 +1,54 @@
1
+ import { jsxs as l, jsx as n } from "react/jsx-runtime";
2
+ import { ZoomOut as d, ZoomIn as p } from "lucide-react";
3
+ import { Button as u } from "../../ui/button.js";
4
+ import { ButtonGroup as h, ButtonGroupText as b } from "../../ui/button-group.js";
5
+ import { cn as f } from "../../../lib/utils.js";
6
+ const x = 2;
7
+ function Z(t, r, o) {
8
+ return Math.min(o, Math.max(r, t));
9
+ }
10
+ function N({
11
+ zoom: t,
12
+ onZoomChange: r,
13
+ step: o = 0.1,
14
+ min: e = 0.5,
15
+ max: i = 2,
16
+ showReadout: m = !0,
17
+ className: s
18
+ }) {
19
+ const a = (c) => r(Z(Number(c.toFixed(x)), e, i));
20
+ return /* @__PURE__ */ l(h, { className: f(s), "data-slot": "plate-zoom-control", children: [
21
+ /* @__PURE__ */ n(
22
+ u,
23
+ {
24
+ type: "button",
25
+ variant: "outline",
26
+ size: "icon-sm",
27
+ "aria-label": "Zoom out",
28
+ disabled: t <= e,
29
+ onClick: () => a(t - o),
30
+ children: /* @__PURE__ */ n(d, { "aria-hidden": !0 })
31
+ }
32
+ ),
33
+ m ? /* @__PURE__ */ l(b, { className: "min-w-[3.25rem] justify-center text-xs tabular-nums", children: [
34
+ Math.round(t * 100),
35
+ "%"
36
+ ] }) : null,
37
+ /* @__PURE__ */ n(
38
+ u,
39
+ {
40
+ type: "button",
41
+ variant: "outline",
42
+ size: "icon-sm",
43
+ "aria-label": "Zoom in",
44
+ disabled: t >= i,
45
+ onClick: () => a(t + o),
46
+ children: /* @__PURE__ */ n(p, { "aria-hidden": !0 })
47
+ }
48
+ )
49
+ ] });
50
+ }
51
+ export {
52
+ N as PlateZoomControl
53
+ };
54
+ //# sourceMappingURL=PlateZoomControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PlateZoomControl.js","sources":["../../../../src/components/composed/PlateMapEditor/PlateZoomControl.tsx"],"sourcesContent":["import { ZoomIn, ZoomOut } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { ButtonGroup, ButtonGroupText } from \"@/components/ui/button-group\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface PlateZoomControlProps {\n zoom: number;\n onZoomChange: (next: number) => void;\n /** Increment per click. Defaults to 0.1. */\n step?: number;\n min?: number;\n max?: number;\n /** Render a percentage readout between the buttons. Defaults to true. */\n showReadout?: boolean;\n className?: string;\n}\n\nconst ZOOM_DECIMALS = 2;\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nexport function PlateZoomControl({\n zoom,\n onZoomChange,\n step = 0.1,\n min = 0.5,\n max = 2,\n showReadout = true,\n className,\n}: PlateZoomControlProps) {\n const setZoom = (next: number) => onZoomChange(clamp(Number(next.toFixed(ZOOM_DECIMALS)), min, max));\n\n return (\n <ButtonGroup className={cn(className)} data-slot=\"plate-zoom-control\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom out\"\n disabled={zoom <= min}\n onClick={() => setZoom(zoom - step)}\n >\n <ZoomOut aria-hidden />\n </Button>\n {showReadout ? (\n <ButtonGroupText className=\"min-w-[3.25rem] justify-center text-xs tabular-nums\">\n {Math.round(zoom * 100)}%\n </ButtonGroupText>\n ) : null}\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label=\"Zoom in\"\n disabled={zoom >= max}\n onClick={() => setZoom(zoom + step)}\n >\n <ZoomIn aria-hidden />\n </Button>\n </ButtonGroup>\n );\n}\n"],"names":["ZOOM_DECIMALS","clamp","value","min","max","PlateZoomControl","zoom","onZoomChange","step","showReadout","className","setZoom","next","ButtonGroup","cn","jsx","Button","ZoomOut","jsxs","ButtonGroupText","ZoomIn"],"mappings":";;;;;AAkBA,MAAMA,IAAgB;AAEtB,SAASC,EAAMC,GAAeC,GAAaC,GAAqB;AAC9D,SAAO,KAAK,IAAIA,GAAK,KAAK,IAAID,GAAKD,CAAK,CAAC;AAC3C;AAEO,SAASG,EAAiB;AAAA,EAC/B,MAAAC;AAAA,EACA,cAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,KAAAL,IAAM;AAAA,EACN,KAAAC,IAAM;AAAA,EACN,aAAAK,IAAc;AAAA,EACd,WAAAC;AACF,GAA0B;AACxB,QAAMC,IAAU,CAACC,MAAiBL,EAAaN,EAAM,OAAOW,EAAK,QAAQZ,CAAa,CAAC,GAAGG,GAAKC,CAAG,CAAC;AAEnG,2BACGS,GAAA,EAAY,WAAWC,EAAGJ,CAAS,GAAG,aAAU,sBAC/C,UAAA;AAAA,IAAA,gBAAAK;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,cAAW;AAAA,QACX,UAAUV,KAAQH;AAAA,QAClB,SAAS,MAAMQ,EAAQL,IAAOE,CAAI;AAAA,QAElC,UAAA,gBAAAO,EAACE,GAAA,EAAQ,eAAW,GAAA,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAEtBR,IACC,gBAAAS,EAACC,GAAA,EAAgB,WAAU,uDACxB,UAAA;AAAA,MAAA,KAAK,MAAMb,IAAO,GAAG;AAAA,MAAE;AAAA,IAAA,EAAA,CAC1B,IACE;AAAA,IACJ,gBAAAS;AAAA,MAACC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,cAAW;AAAA,QACX,UAAUV,KAAQF;AAAA,QAClB,SAAS,MAAMO,EAAQL,IAAOE,CAAI;AAAA,QAElC,UAAA,gBAAAO,EAACK,GAAA,EAAO,eAAW,GAAA,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EACtB,GACF;AAEJ;"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),j=require("lucide-react"),O=require("react"),q=require("./csvPlateTriage.cjs"),P=require("./helpers.cjs"),m=require("../../ui/button.cjs"),k=require("../../ui/label.cjs"),s=require("../../ui/select.cjs"),f=require("../../ui/separator.cjs"),y=require("../../../lib/utils.cjs");function w(t){const a=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(t){for(const l in t)if(l!=="default"){const i=Object.getOwnPropertyDescriptor(t,l);Object.defineProperty(a,l,i.get?i:{enumerable:!0,get:()=>t[l]})}}return a.default=t,Object.freeze(a)}const g=w(O);function h({label:t,accept:a,onPick:l,disabled:i,triageCsv:o}){const r=g.useRef(null);return e.jsxs(e.Fragment,{children:[e.jsxs(m.Button,{type:"button",variant:"outline",size:"sm",disabled:i,onClick:()=>r.current?.click(),children:[e.jsx(j.Upload,{"aria-hidden":!0})," ",t]}),e.jsx("input",{ref:r,type:"file",accept:a,hidden:!0,onChange:c=>{const u=c.currentTarget,n=c.target.files?.[0];if(!n){u.value="";return}(async()=>{const x=o?await q.triagePlateMapCsvFile(n):void 0;await(x?l(n,x):l(n)),u.value=""})()}})]})}function z({templates:t,templateId:a,onTemplateChange:l,onClearTemplate:i,hasEntries:o,onImportCsv:r,onExportCsv:c,onImportTemplate:u,onExportTemplate:n,className:x,csvAccept:v=".csv,text/csv",templateAccept:b="application/json"}){const S=g.useMemo(()=>P.groupTemplateOptions(t),[t]);return e.jsxs("div",{"data-slot":"template-io-panel",className:y.cn("flex flex-col gap-3",x),children:[e.jsx("div",{className:"text-sm font-medium",children:"Template & import / export"}),t&&l?e.jsxs("div",{className:"flex flex-col gap-1.5",children:[e.jsx(k.Label,{htmlFor:"plate-template",children:"Template"}),e.jsxs(s.Select,{value:a??"",onValueChange:d=>l(d),children:[e.jsx(s.SelectTrigger,{id:"plate-template",size:"sm",className:"w-full",children:e.jsx(s.SelectValue,{placeholder:"Select template…"})}),e.jsx(s.SelectContent,{children:S.map(([d,N])=>e.jsxs(s.SelectGroup,{children:[d?e.jsx(s.SelectLabel,{children:d}):null,N.map(p=>e.jsx(s.SelectItem,{value:p.id,children:p.label},p.id))]},d||"_"))})]}),i?e.jsx(m.Button,{variant:"outline",size:"sm",onClick:i,disabled:!a&&!o,children:"Clear template"}):null]}):null,u||n?e.jsxs(e.Fragment,{children:[e.jsx(f.Separator,{}),e.jsx("div",{className:"text-xs font-medium text-muted-foreground",children:"Template"}),e.jsxs("div",{className:"flex flex-col gap-2",children:[u?e.jsx(h,{label:"Import template (JSON)",accept:b,onPick:u}):null,n?e.jsxs(m.Button,{variant:"outline",size:"sm",disabled:!o,onClick:n,children:[e.jsx(j.Download,{"aria-hidden":!0})," Export template (JSON)"]}):null]})]}):null,r||c?e.jsxs(e.Fragment,{children:[e.jsx(f.Separator,{}),e.jsx("div",{className:"text-xs font-medium text-muted-foreground",children:"Plate map"}),e.jsxs("div",{className:"flex flex-col gap-2",children:[r?e.jsx(h,{label:"Import plate map (CSV)",accept:v,onPick:r,triageCsv:!0}):null,c?e.jsxs(m.Button,{variant:"outline",size:"sm",disabled:!o,onClick:c,children:[e.jsx(j.Download,{"aria-hidden":!0})," Export plate map (CSV)"]}):null]})]}):null]})}exports.TemplateIOPanel=z;
2
+ //# sourceMappingURL=TemplateIOPanel.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateIOPanel.cjs","sources":["../../../../src/components/composed/PlateMapEditor/TemplateIOPanel.tsx"],"sourcesContent":["import { Download, Upload } from \"lucide-react\";\nimport * as React from \"react\";\n\nimport { triagePlateMapCsvFile } from \"./csvPlateTriage\";\nimport { groupTemplateOptions } from \"./helpers\";\n\nimport type { ImportExportHandlers, PlateMapCsvTriage, TemplateOption } from \"./types\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface TemplateIOPanelProps extends ImportExportHandlers {\n templates?: TemplateOption[];\n templateId?: string;\n onTemplateChange?: (id: string) => void;\n onClearTemplate?: () => void;\n /** Disable export buttons when there's nothing to export. */\n hasEntries?: boolean;\n className?: string;\n csvAccept?: string;\n templateAccept?: string;\n}\n\ninterface FilePickerButtonProps {\n label: string;\n accept: string;\n onPick: (file: File, triage?: PlateMapCsvTriage) => void | Promise<void>;\n disabled?: boolean;\n triageCsv?: boolean;\n}\n\nfunction FilePickerButton({ label, accept, onPick, disabled, triageCsv }: FilePickerButtonProps) {\n const inputRef = React.useRef<HTMLInputElement>(null);\n return (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" disabled={disabled} onClick={() => inputRef.current?.click()}>\n <Upload aria-hidden /> {label}\n </Button>\n <input\n ref={inputRef}\n type=\"file\"\n accept={accept}\n hidden\n onChange={(e) => {\n const input = e.currentTarget;\n const f = e.target.files?.[0];\n if (!f) {\n input.value = \"\";\n return;\n }\n\n void (async () => {\n const triage = triageCsv ? await triagePlateMapCsvFile(f) : undefined;\n await (triage ? onPick(f, triage) : onPick(f));\n input.value = \"\";\n })();\n }}\n />\n </>\n );\n}\n\nexport function TemplateIOPanel({\n templates,\n templateId,\n onTemplateChange,\n onClearTemplate,\n hasEntries,\n onImportCsv,\n onExportCsv,\n onImportTemplate,\n onExportTemplate,\n className,\n csvAccept = \".csv,text/csv\",\n templateAccept = \"application/json\",\n}: TemplateIOPanelProps) {\n const groups = React.useMemo(() => groupTemplateOptions(templates), [templates]);\n\n return (\n <div data-slot=\"template-io-panel\" className={cn(\"flex flex-col gap-3\", className)}>\n <div className=\"text-sm font-medium\">Template &amp; import / export</div>\n\n {templates && onTemplateChange ? (\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"plate-template\">Template</Label>\n <Select value={templateId ?? \"\"} onValueChange={(v) => onTemplateChange(v)}>\n <SelectTrigger id=\"plate-template\" size=\"sm\" className=\"w-full\">\n <SelectValue placeholder=\"Select template…\" />\n </SelectTrigger>\n <SelectContent>\n {groups.map(([group, opts]) => (\n <SelectGroup key={group || \"_\"}>\n {group ? <SelectLabel>{group}</SelectLabel> : null}\n {opts.map((t) => (\n <SelectItem key={t.id} value={t.id}>\n {t.label}\n </SelectItem>\n ))}\n </SelectGroup>\n ))}\n </SelectContent>\n </Select>\n {onClearTemplate ? (\n <Button variant=\"outline\" size=\"sm\" onClick={onClearTemplate} disabled={!templateId && !hasEntries}>\n Clear template\n </Button>\n ) : null}\n </div>\n ) : null}\n\n {onImportTemplate || onExportTemplate ? (\n <>\n <Separator />\n <div className=\"text-xs font-medium text-muted-foreground\">Template</div>\n <div className=\"flex flex-col gap-2\">\n {onImportTemplate ? (\n <FilePickerButton label=\"Import template (JSON)\" accept={templateAccept} onPick={onImportTemplate} />\n ) : null}\n {onExportTemplate ? (\n <Button variant=\"outline\" size=\"sm\" disabled={!hasEntries} onClick={onExportTemplate}>\n <Download aria-hidden /> Export template (JSON)\n </Button>\n ) : null}\n </div>\n </>\n ) : null}\n\n {onImportCsv || onExportCsv ? (\n <>\n <Separator />\n <div className=\"text-xs font-medium text-muted-foreground\">Plate map</div>\n <div className=\"flex flex-col gap-2\">\n {onImportCsv ? (\n <FilePickerButton label=\"Import plate map (CSV)\" accept={csvAccept} onPick={onImportCsv} triageCsv />\n ) : null}\n {onExportCsv ? (\n <Button variant=\"outline\" size=\"sm\" disabled={!hasEntries} onClick={onExportCsv}>\n <Download aria-hidden /> Export plate map (CSV)\n </Button>\n ) : null}\n </div>\n </>\n ) : null}\n </div>\n );\n}\n"],"names":["FilePickerButton","label","accept","onPick","disabled","triageCsv","inputRef","React","jsxs","Fragment","Button","jsx","Upload","e","input","f","triage","triagePlateMapCsvFile","TemplateIOPanel","templates","templateId","onTemplateChange","onClearTemplate","hasEntries","onImportCsv","onExportCsv","onImportTemplate","onExportTemplate","className","csvAccept","templateAccept","groups","groupTemplateOptions","cn","Label","Select","v","SelectTrigger","SelectValue","SelectContent","group","opts","SelectGroup","SelectLabel","t","SelectItem","Separator","Download"],"mappings":"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"}