@broberg/bodymap 0.1.2 → 0.1.4

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.
package/dist/react.js CHANGED
@@ -39,6 +39,9 @@ var REGIONS = [
39
39
  ];
40
40
  var REGION_KEY_SET = new Set(REGIONS.map((r) => r.key));
41
41
  REGIONS.map((r) => r.key);
42
+ function getRegion(key) {
43
+ return REGIONS.find((r) => r.key === key);
44
+ }
42
45
  var PAIN_TYPES = ["stikkende", "dump", "konstant", "jagende"];
43
46
  var painPointSchema = z.object({
44
47
  region: z.string().refine((k) => REGION_KEY_SET.has(k), { message: "unknown region" }),
@@ -55,6 +58,18 @@ function isSelectable(key, config = {}) {
55
58
  if (s?.visible === false) return false;
56
59
  return s?.selectable ?? true;
57
60
  }
61
+ var defaultPalette = {
62
+ body: "#d2d7de",
63
+ hover: "#8fd0cd",
64
+ selected: "#5cc4b7",
65
+ heat: { low: "#fcd34d", mid: "#fb923c", high: "#ef4444" }
66
+ };
67
+ function heatFor(intensity, palette = defaultPalette) {
68
+ return intensity >= 7 ? palette.heat.high : intensity >= 4 ? palette.heat.mid : palette.heat.low;
69
+ }
70
+ function baseColorFor(regionKey, palette = defaultPalette) {
71
+ return palette.regions?.[regionKey] ?? palette.body;
72
+ }
58
73
  z.object({
59
74
  schema: z.literal("bodymap/v1"),
60
75
  view: z.enum(["front", "back", "left", "right"]),
@@ -165,6 +180,84 @@ var FILL_BACK = {
165
180
  hip_left: "#c99bd6",
166
181
  hip_right: "#c99bd6"
167
182
  };
183
+ var daRegions = Object.fromEntries(REGIONS.map((r) => [r.key, r.label]));
184
+ var EN_CODE = {
185
+ HEAD: "Head",
186
+ NECK: "Neck",
187
+ CHEST: "Chest",
188
+ THORA: "Upper back",
189
+ LUMBAR: "Lower back",
190
+ GROIN: "Groin",
191
+ SHOULDER: "Shoulder",
192
+ UARM: "Upper arm",
193
+ ELBOW: "Elbow",
194
+ FARM: "Forearm",
195
+ WRIST: "Wrist",
196
+ HAND: "Hand",
197
+ HIP: "Hip",
198
+ THIGH: "Thigh",
199
+ KNEE: "Knee",
200
+ LOWLEG: "Lower leg",
201
+ ANKLE: "Ankle",
202
+ FOOT: "Foot"
203
+ };
204
+ var enRegions = Object.fromEntries(
205
+ REGIONS.map((r) => [r.key, EN_CODE[r.code] + (r.side ? r.side === "left" ? ", left" : ", right" : "")])
206
+ );
207
+ var LABELS_DA = {
208
+ regions: daRegions,
209
+ qualities: { stikkende: "stikkende", dump: "dump", konstant: "konstant", jagende: "jagende" },
210
+ intensity: "Intensitet (0-10)",
211
+ quality: "Kvalitet",
212
+ remove: "Fjern punkt",
213
+ front: "Forfra",
214
+ back: "Bagfra",
215
+ empty: "V\xE6lg en kropsdel for at markere smerte.",
216
+ before: "F\xF8r",
217
+ after: "Efter",
218
+ noChange: "Ingen \xE6ndring",
219
+ change: { new: "nyt", resolved: "forsvundet", improved: "bedre", worse: "v\xE6rre" },
220
+ ariaMarked: (n, i, q) => `${n}, smerte ${i} af 10${q ? ", " + q : ""}`,
221
+ ariaUnmarked: (n) => `${n}, ikke markeret. Aktiv\xE9r for at markere smerte.`,
222
+ svgLabel: "Kropskort \u2014 v\xE6lg hvor det g\xF8r ondt",
223
+ viewLabel: "Visning",
224
+ zoomLabel: "Zoom",
225
+ zoomIn: "Zoom ind",
226
+ zoomOut: "Zoom ud",
227
+ zoomReset: "Nulstil zoom"
228
+ };
229
+ var LABELS_EN = {
230
+ regions: enRegions,
231
+ qualities: { stikkende: "stabbing", dump: "dull", konstant: "constant", jagende: "shooting" },
232
+ intensity: "Intensity (0-10)",
233
+ quality: "Quality",
234
+ remove: "Remove point",
235
+ front: "Front",
236
+ back: "Back",
237
+ empty: "Pick a body part to mark pain.",
238
+ before: "Before",
239
+ after: "After",
240
+ noChange: "No change",
241
+ change: { new: "new", resolved: "resolved", improved: "improved", worse: "worse" },
242
+ ariaMarked: (n, i, q) => `${n}, pain ${i} of 10${q ? ", " + q : ""}`,
243
+ ariaUnmarked: (n) => `${n}, not marked. Activate to mark pain.`,
244
+ svgLabel: "Body map \u2014 pick where it hurts",
245
+ viewLabel: "View",
246
+ zoomLabel: "Zoom",
247
+ zoomIn: "Zoom in",
248
+ zoomOut: "Zoom out",
249
+ zoomReset: "Reset zoom"
250
+ };
251
+ function resolveLabels(locale, overrides) {
252
+ const base = locale === "en" ? LABELS_EN : LABELS_DA;
253
+ if (!overrides) return base;
254
+ return {
255
+ ...base,
256
+ ...overrides,
257
+ regions: { ...base.regions, ...overrides.regions },
258
+ qualities: { ...base.qualities, ...overrides.qualities }
259
+ };
260
+ }
168
261
  var VBW = 260;
169
262
  var VBH = 470;
170
263
  var MIN_HIT = 18;
@@ -184,6 +277,15 @@ function hitShape(s) {
184
277
  function heat(v) {
185
278
  return v >= 7 ? "#ef4444" : v >= 4 ? "#fb923c" : "#fcd34d";
186
279
  }
280
+ function heatTier(v) {
281
+ return v >= 7 ? "high" : v >= 4 ? "mid" : "low";
282
+ }
283
+ function baseFill(key, rainbow, palette) {
284
+ return palette ? baseColorFor(key, palette) : `var(--bmap-region-${key}, var(--bmap-body, ${rainbow}))`;
285
+ }
286
+ function markedFill(v, palette) {
287
+ return palette ? heatFor(v, palette) : `var(--bmap-heat-${heatTier(v)}, ${heat(v)})`;
288
+ }
187
289
  function shapeEl(s, props) {
188
290
  if (s.el === "circle") return /* @__PURE__ */ jsx("circle", { cx: s.cx, cy: s.cy, r: s.r, ...props });
189
291
  if (s.el === "ellipse") return /* @__PURE__ */ jsx("ellipse", { cx: s.cx, cy: s.cy, rx: s.rx, ry: s.ry, ...props });
@@ -196,7 +298,7 @@ var STYLE = `
196
298
  .bmap__stage{display:flex;flex-direction:column;gap:10px;flex:0 1 340px;min-width:0;max-width:360px}
197
299
  .bmap__bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
198
300
  .bmap__viewtoggle{display:inline-flex;background:#f1f5f9;border:1px solid var(--bmap-line);border-radius:10px;padding:3px;gap:2px}
199
- .bmap__vbtn{font:inherit;font-size:13px;font-weight:600;color:var(--bmap-muted);background:none;border:0;border-radius:7px;padding:8px 16px;cursor:pointer;transition:color .12s,background .12s,transform .1s}
301
+ .bmap__vbtn{font:inherit;font-size:13px;font-weight:600;color:#556274;background:none;border:0;border-radius:7px;padding:8px 16px;cursor:pointer;transition:color .12s,background .12s,transform .1s}
200
302
  .bmap__vbtn:hover{color:var(--bmap-ink)}
201
303
  .bmap__vbtn:active{transform:scale(.97)}
202
304
  .bmap__vbtn--on{background:#fff;color:var(--bmap-ink);box-shadow:0 1px 2px rgba(15,23,42,.14)}
@@ -233,6 +335,23 @@ var STYLE = `
233
335
  .bmap__rm:hover{background:#fef2f2}
234
336
  .bmap__rm:active{transform:scale(.97)}
235
337
  .bmap__i:focus-visible,.bmap__chip:focus-visible,.bmap__vbtn:focus-visible,.bmap__rm:focus-visible,.bmap__zoom button:focus-visible{outline:2px solid var(--bmap-accent);outline-offset:2px}
338
+ .bmapc{--bmap-accent:var(--primary,#0e8f8a);--bmap-line:#e2e8f0;--bmap-ink:#1e293b;--bmap-muted:#64748b;font:15px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif;color:var(--bmap-ink)}
339
+ .bmapc__bodies{display:flex;gap:14px;flex-wrap:wrap}
340
+ .bmapc__fig{flex:1 1 150px;min-width:130px;max-width:220px;margin:0;text-align:center}
341
+ .bmapc__cap{font-size:12px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:#556274;margin-bottom:6px}
342
+ .bmapc__svg{width:100%;height:auto;display:block}
343
+ .bmapc__vis{stroke:#fff;stroke-width:2.4}
344
+ .bmapc__n{font:700 12px ui-sans-serif;fill:#fff;pointer-events:none}
345
+ .bmapc__delta{margin-top:16px;display:flex;flex-direction:column;gap:6px;max-width:460px}
346
+ .bmapc__row{display:flex;align-items:center;gap:10px;font-size:13.5px;border:1px solid var(--bmap-line);border-radius:9px;padding:7px 11px}
347
+ .bmapc__rname{font-weight:600;flex:1 1 auto}
348
+ .bmapc__rval{font:12px ui-monospace,monospace;color:#556274;white-space:nowrap}
349
+ .bmapc__badge{font-size:11px;font-weight:700;border-radius:6px;padding:2px 8px;white-space:nowrap}
350
+ .bmapc__badge--improved{background:#dcfce7;color:#166534}
351
+ .bmapc__badge--worse{background:#fee2e2;color:#991b1b}
352
+ .bmapc__badge--new{background:#fef3c7;color:#92400e}
353
+ .bmapc__badge--resolved{background:#e2e8f0;color:#475569}
354
+ .bmapc__empty{color:#556274;font-size:13.5px;margin-top:14px}
236
355
  @media (max-width:560px){
237
356
  .bmap{flex-direction:column;gap:14px}
238
357
  .bmap__stage{max-width:100%;width:100%;flex-basis:auto}
@@ -264,6 +383,10 @@ function BodyMap({
264
383
  config = {},
265
384
  defaultView = "front",
266
385
  onViewChange,
386
+ palette,
387
+ locale = "da",
388
+ labels,
389
+ readOnly = false,
267
390
  className
268
391
  }) {
269
392
  useEffect(ensureStyles, []);
@@ -275,6 +398,8 @@ function BodyMap({
275
398
  const report = value ?? internal;
276
399
  const shapes = view === "back" ? SHAPES_BACK : SHAPES;
277
400
  const fills = view === "back" ? FILL_BACK : FILL;
401
+ const L = resolveLabels(locale, labels);
402
+ const nameOf = (key) => L.regions[key] ?? getRegion(key)?.label ?? key;
278
403
  const svgRef = useRef(null);
279
404
  const zoomRef = useRef(1);
280
405
  const panRef = useRef({ x: 0, y: 0 });
@@ -376,14 +501,15 @@ function BodyMap({
376
501
  const region = selected ? REGIONS.find((r) => r.key === selected) : void 0;
377
502
  const current = selected ? pointOf(selected) : void 0;
378
503
  const rootCls = ["bmap", className].filter(Boolean).join(" ");
379
- const ariaFor = (r, marked, selectable = true) => {
380
- if (marked) return `${r.label}, smerte ${marked.intensity} af 10${marked.type ? ", " + marked.type : ""}`;
381
- return selectable ? `${r.label}, ikke markeret. Aktiv\xE9r for at markere smerte.` : r.label;
504
+ const rootStyle = palette ? { "--bmap-accent": palette.selected } : void 0;
505
+ const ariaFor = (r, marked, interactive = true) => {
506
+ if (marked) return L.ariaMarked(nameOf(r.key), marked.intensity, marked.type ? L.qualities[marked.type] : void 0);
507
+ return interactive ? L.ariaUnmarked(nameOf(r.key)) : nameOf(r.key);
382
508
  };
383
- return /* @__PURE__ */ jsxs("div", { className: rootCls, "data-testid": "bodymap-root", children: [
509
+ return /* @__PURE__ */ jsxs("div", { className: rootCls, "data-testid": "bodymap-root", style: rootStyle, children: [
384
510
  /* @__PURE__ */ jsxs("div", { className: "bmap__stage", children: [
385
511
  /* @__PURE__ */ jsxs("div", { className: "bmap__bar", children: [
386
- /* @__PURE__ */ jsxs("div", { className: "bmap__viewtoggle", role: "group", "aria-label": "Visning", children: [
512
+ /* @__PURE__ */ jsxs("div", { className: "bmap__viewtoggle", role: "group", "aria-label": L.viewLabel, children: [
387
513
  /* @__PURE__ */ jsx(
388
514
  "button",
389
515
  {
@@ -392,7 +518,7 @@ function BodyMap({
392
518
  "data-testid": "bodymap-view-front",
393
519
  "aria-pressed": view === "front",
394
520
  onClick: () => changeView("front"),
395
- children: "Forfra"
521
+ children: L.front
396
522
  }
397
523
  ),
398
524
  /* @__PURE__ */ jsx(
@@ -403,14 +529,14 @@ function BodyMap({
403
529
  "data-testid": "bodymap-view-back",
404
530
  "aria-pressed": view === "back",
405
531
  onClick: () => changeView("back"),
406
- children: "Bagfra"
532
+ children: L.back
407
533
  }
408
534
  )
409
535
  ] }),
410
- /* @__PURE__ */ jsxs("div", { className: "bmap__zoom", role: "group", "aria-label": "Zoom", children: [
411
- /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "bodymap-zoom-out", "aria-label": "Zoom ud", disabled: zoom <= ZMIN, onClick: () => zoomBtn(1 / 1.4), children: "\u2212" }),
412
- /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "bodymap-zoom-reset", "aria-label": "Nulstil zoom", disabled: zoom === 1 && pan.x === 0 && pan.y === 0, onClick: resetZoom, children: "\u2922" }),
413
- /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "bodymap-zoom-in", "aria-label": "Zoom ind", disabled: zoom >= ZMAX, onClick: () => zoomBtn(1.4), children: "+" })
536
+ /* @__PURE__ */ jsxs("div", { className: "bmap__zoom", role: "group", "aria-label": L.zoomLabel, children: [
537
+ /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "bodymap-zoom-out", "aria-label": L.zoomOut, disabled: zoom <= ZMIN, onClick: () => zoomBtn(1 / 1.4), children: "\u2212" }),
538
+ /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "bodymap-zoom-reset", "aria-label": L.zoomReset, disabled: zoom === 1 && pan.x === 0 && pan.y === 0, onClick: resetZoom, children: "\u2922" }),
539
+ /* @__PURE__ */ jsx("button", { type: "button", "data-testid": "bodymap-zoom-in", "aria-label": L.zoomIn, disabled: zoom >= ZMAX, onClick: () => zoomBtn(1.4), children: "+" })
414
540
  ] })
415
541
  ] }),
416
542
  /* @__PURE__ */ jsx(
@@ -420,7 +546,7 @@ function BodyMap({
420
546
  className: "bmap__svg",
421
547
  viewBox: "0 0 260 470",
422
548
  role: "group",
423
- "aria-label": "Kropskort \u2014 v\xE6lg hvor det g\xF8r ondt",
549
+ "aria-label": L.svgLabel,
424
550
  onPointerDown,
425
551
  onPointerMove,
426
552
  onPointerUp,
@@ -431,21 +557,20 @@ function BodyMap({
431
557
  const s = shapes[r.key];
432
558
  if (!s) return null;
433
559
  const marked = pointOf(r.key);
434
- const selectable = isSelectable(r.key, config);
560
+ const interactive = isSelectable(r.key, config) && !readOnly;
435
561
  const visCls = ["bmap__vis", selected === r.key && "bmap__vis--sel"].filter(Boolean).join(" ");
436
- const fill = marked ? heat(marked.intensity) : fills[r.key] ?? "#cbd5e1";
562
+ const fill = marked ? markedFill(marked.intensity, palette) : baseFill(r.key, fills[r.key] ?? "#cbd5e1", palette);
437
563
  const hs = hitShape(s);
438
- const act = () => selectable ? pickRegion(r.key) : void 0;
439
564
  return /* @__PURE__ */ jsxs("g", { children: [
440
565
  shapeEl(hs, {
441
- className: "bmap__hit" + (selectable ? "" : " bmap__hit--locked"),
566
+ className: "bmap__hit" + (interactive ? "" : " bmap__hit--locked"),
442
567
  "data-testid": `bodymap-region-${r.key}`,
443
- role: selectable ? "button" : "img",
444
- tabIndex: selectable ? 0 : -1,
445
- "aria-label": ariaFor(r, marked, selectable),
446
- "aria-pressed": selectable ? selected === r.key : void 0,
447
- onClick: act,
448
- onKeyDown: selectable ? (e) => {
568
+ role: interactive ? "button" : "img",
569
+ tabIndex: interactive ? 0 : -1,
570
+ "aria-label": ariaFor(r, marked, interactive),
571
+ "aria-pressed": interactive ? selected === r.key : void 0,
572
+ onClick: interactive ? () => pickRegion(r.key) : void 0,
573
+ onKeyDown: interactive ? (e) => {
449
574
  if (e.key === "Enter" || e.key === " ") {
450
575
  e.preventDefault();
451
576
  suppressClick.current = false;
@@ -453,7 +578,7 @@ function BodyMap({
453
578
  }
454
579
  } : void 0
455
580
  }),
456
- shapeEl(s, { className: visCls, fill, opacity: selectable ? 1 : 0.4 })
581
+ shapeEl(s, { className: visCls, fill, opacity: !readOnly && !isSelectable(r.key, config) ? 0.4 : 1 })
457
582
  ] }, r.key);
458
583
  }),
459
584
  report.map((p) => {
@@ -466,26 +591,25 @@ function BodyMap({
466
591
  }
467
592
  )
468
593
  ] }),
469
- region ? /* @__PURE__ */ jsxs("div", { className: "bmap__panel", "data-testid": "bodymap-panel", children: [
594
+ readOnly ? null : region ? /* @__PURE__ */ jsxs("div", { className: "bmap__panel", "data-testid": "bodymap-panel", children: [
470
595
  /* @__PURE__ */ jsxs("div", { className: "bmap__ph", children: [
471
- /* @__PURE__ */ jsx("b", { children: region.label }),
596
+ /* @__PURE__ */ jsx("b", { children: nameOf(region.key) }),
472
597
  /* @__PURE__ */ jsx("span", { className: "bmap__code", children: region.code })
473
598
  ] }),
474
- /* @__PURE__ */ jsx("p", { className: "bmap__lbl", id: "bmap-intensity-lbl", children: "Intensitet (0-10)" }),
599
+ /* @__PURE__ */ jsx("p", { className: "bmap__lbl", id: "bmap-intensity-lbl", children: L.intensity }),
475
600
  /* @__PURE__ */ jsx("div", { className: "bmap__scale", role: "group", "aria-labelledby": "bmap-intensity-lbl", children: Array.from({ length: 11 }, (_, n) => /* @__PURE__ */ jsx(
476
601
  "button",
477
602
  {
478
603
  type: "button",
479
604
  className: "bmap__i" + (current?.intensity === n ? " bmap__i--on" : ""),
480
605
  "data-testid": `bodymap-intensity-${n}`,
481
- "aria-label": `Intensitet ${n}`,
482
606
  "aria-pressed": current?.intensity === n,
483
607
  onClick: () => setPain(region.key, n, current?.type),
484
608
  children: n
485
609
  },
486
610
  n
487
611
  )) }),
488
- /* @__PURE__ */ jsx("p", { className: "bmap__lbl", id: "bmap-quality-lbl", children: "Kvalitet" }),
612
+ /* @__PURE__ */ jsx("p", { className: "bmap__lbl", id: "bmap-quality-lbl", children: L.quality }),
489
613
  /* @__PURE__ */ jsx("div", { className: "bmap__chips", role: "group", "aria-labelledby": "bmap-quality-lbl", children: PAIN_TYPES.map((t) => /* @__PURE__ */ jsx(
490
614
  "button",
491
615
  {
@@ -494,15 +618,84 @@ function BodyMap({
494
618
  "data-testid": `bodymap-type-${t}`,
495
619
  "aria-pressed": current?.type === t,
496
620
  onClick: () => setPain(region.key, current?.intensity ?? 5, t),
497
- children: t
621
+ children: L.qualities[t]
498
622
  },
499
623
  t
500
624
  )) }),
501
- current && /* @__PURE__ */ jsx("button", { type: "button", className: "bmap__rm", "data-testid": "bodymap-remove", onClick: () => removePain(region.key), children: "Fjern punkt" })
502
- ] }) : /* @__PURE__ */ jsx("div", { className: "bmap__panel bmap__panel--empty", "data-testid": "bodymap-panel", children: "V\xE6lg en kropsdel for at markere smerte." })
625
+ current && /* @__PURE__ */ jsx("button", { type: "button", className: "bmap__rm", "data-testid": "bodymap-remove", onClick: () => removePain(region.key), children: L.remove })
626
+ ] }) : /* @__PURE__ */ jsx("div", { className: "bmap__panel bmap__panel--empty", "data-testid": "bodymap-panel", children: L.empty })
627
+ ] });
628
+ }
629
+ function changeOf(b, a) {
630
+ if (b == null && a != null) return "new";
631
+ if (b != null && a == null) return "resolved";
632
+ if (b == null || a == null) return "same";
633
+ return a < b ? "improved" : a > b ? "worse" : "same";
634
+ }
635
+ function CompareFigure({ report, view, palette, caption, testid }) {
636
+ const shapes = view === "back" ? SHAPES_BACK : SHAPES;
637
+ const fills = view === "back" ? FILL_BACK : FILL;
638
+ return /* @__PURE__ */ jsxs("figure", { className: "bmapc__fig", "data-testid": testid, children: [
639
+ /* @__PURE__ */ jsx("figcaption", { className: "bmapc__cap", children: caption }),
640
+ /* @__PURE__ */ jsxs("svg", { className: "bmapc__svg", viewBox: "0 0 260 470", role: "img", "aria-label": caption, children: [
641
+ REGIONS.map((r) => {
642
+ const s = shapes[r.key];
643
+ if (!s) return null;
644
+ const m = report.find((p) => p.region === r.key);
645
+ const fill = m ? markedFill(m.intensity, palette) : baseFill(r.key, fills[r.key] ?? "#cbd5e1", palette);
646
+ return /* @__PURE__ */ jsx("g", { children: shapeEl(s, { className: "bmapc__vis", fill }) }, r.key);
647
+ }),
648
+ report.map((p) => {
649
+ const s = shapes[p.region];
650
+ if (!s) return null;
651
+ const c = center(s);
652
+ return /* @__PURE__ */ jsx("text", { x: c.x, y: c.y + 4, textAnchor: "middle", className: "bmapc__n", children: p.intensity }, p.region);
653
+ })
654
+ ] })
655
+ ] });
656
+ }
657
+ function BodyMapCompare({
658
+ before,
659
+ after,
660
+ palette,
661
+ locale = "da",
662
+ labels,
663
+ defaultView = "front",
664
+ className
665
+ }) {
666
+ useEffect(ensureStyles, []);
667
+ const [view, setView] = useState(defaultView);
668
+ const L = resolveLabels(locale, labels);
669
+ const nameOf = (k) => L.regions[k] ?? getRegion(k)?.label ?? k;
670
+ const rootStyle = palette ? { "--bmap-accent": palette.selected } : void 0;
671
+ const keys = Array.from(new Set([...before, ...after].map((p) => p.region)));
672
+ const changes = keys.map((k) => ({
673
+ key: k,
674
+ b: before.find((p) => p.region === k)?.intensity,
675
+ a: after.find((p) => p.region === k)?.intensity,
676
+ status: changeOf(before.find((p) => p.region === k)?.intensity, after.find((p) => p.region === k)?.intensity)
677
+ })).filter((c) => c.status !== "same").sort((x, y) => x.key < y.key ? -1 : 1);
678
+ return /* @__PURE__ */ jsxs("div", { className: ["bmapc", className].filter(Boolean).join(" "), "data-testid": "bodymap-compare", style: rootStyle, children: [
679
+ /* @__PURE__ */ jsxs("div", { className: "bmap__viewtoggle", role: "group", "aria-label": L.viewLabel, style: { marginBottom: 12 }, children: [
680
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bmap__vbtn" + (view === "front" ? " bmap__vbtn--on" : ""), "data-testid": "bodymap-compare-view-front", "aria-pressed": view === "front", onClick: () => setView("front"), children: L.front }),
681
+ /* @__PURE__ */ jsx("button", { type: "button", className: "bmap__vbtn" + (view === "back" ? " bmap__vbtn--on" : ""), "data-testid": "bodymap-compare-view-back", "aria-pressed": view === "back", onClick: () => setView("back"), children: L.back })
682
+ ] }),
683
+ /* @__PURE__ */ jsxs("div", { className: "bmapc__bodies", children: [
684
+ /* @__PURE__ */ jsx(CompareFigure, { report: before, view, palette, caption: L.before, testid: "bodymap-compare-before" }),
685
+ /* @__PURE__ */ jsx(CompareFigure, { report: after, view, palette, caption: L.after, testid: "bodymap-compare-after" })
686
+ ] }),
687
+ changes.length ? /* @__PURE__ */ jsx("div", { className: "bmapc__delta", "data-testid": "bodymap-compare-delta", children: changes.map((c) => /* @__PURE__ */ jsxs("div", { className: "bmapc__row", "data-testid": `bodymap-compare-delta-${c.key}`, children: [
688
+ /* @__PURE__ */ jsx("span", { className: "bmapc__rname", children: nameOf(c.key) }),
689
+ /* @__PURE__ */ jsxs("span", { className: "bmapc__rval", children: [
690
+ c.b ?? "\u2013",
691
+ " \u2192 ",
692
+ c.a ?? "\u2013"
693
+ ] }),
694
+ /* @__PURE__ */ jsx("span", { className: `bmapc__badge bmapc__badge--${c.status}`, children: L.change[c.status] })
695
+ ] }, c.key)) }) : /* @__PURE__ */ jsx("div", { className: "bmapc__empty", "data-testid": "bodymap-compare-delta", children: L.noChange })
503
696
  ] });
504
697
  }
505
698
 
506
- export { BodyMap };
699
+ export { BodyMap, BodyMapCompare, LABELS_DA, LABELS_EN };
507
700
  //# sourceMappingURL=react.js.map
508
701
  //# sourceMappingURL=react.js.map