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