@broberg/bodymap 0.1.1 → 0.1.2
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 +206 -64
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +207 -65
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
1
|
+
import { useEffect, useState, useRef } from 'react';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -165,48 +165,83 @@ var FILL_BACK = {
|
|
|
165
165
|
hip_left: "#c99bd6",
|
|
166
166
|
hip_right: "#c99bd6"
|
|
167
167
|
};
|
|
168
|
+
var VBW = 260;
|
|
169
|
+
var VBH = 470;
|
|
170
|
+
var MIN_HIT = 18;
|
|
168
171
|
function center(s) {
|
|
169
172
|
if (s.el === "rect") return { x: s.x + s.width / 2, y: s.y + s.height / 2 };
|
|
170
173
|
return { x: s.cx, y: s.cy };
|
|
171
174
|
}
|
|
175
|
+
function hitShape(s) {
|
|
176
|
+
if (s.el === "circle") return { el: "circle", cx: s.cx, cy: s.cy, r: Math.max(s.r, MIN_HIT) };
|
|
177
|
+
if (s.el === "ellipse")
|
|
178
|
+
return { el: "ellipse", cx: s.cx, cy: s.cy, rx: Math.max(s.rx, MIN_HIT), ry: Math.max(s.ry, MIN_HIT) };
|
|
179
|
+
const c = center(s);
|
|
180
|
+
const w = Math.max(s.width, MIN_HIT * 2);
|
|
181
|
+
const h = Math.max(s.height, MIN_HIT * 2);
|
|
182
|
+
return { el: "rect", x: c.x - w / 2, y: c.y - h / 2, width: w, height: h, rx: s.rx };
|
|
183
|
+
}
|
|
172
184
|
function heat(v) {
|
|
173
185
|
return v >= 7 ? "#ef4444" : v >= 4 ? "#fb923c" : "#fcd34d";
|
|
174
186
|
}
|
|
187
|
+
function shapeEl(s, props) {
|
|
188
|
+
if (s.el === "circle") return /* @__PURE__ */ jsx("circle", { cx: s.cx, cy: s.cy, r: s.r, ...props });
|
|
189
|
+
if (s.el === "ellipse") return /* @__PURE__ */ jsx("ellipse", { cx: s.cx, cy: s.cy, rx: s.rx, ry: s.ry, ...props });
|
|
190
|
+
return /* @__PURE__ */ jsx("rect", { x: s.x, y: s.y, width: s.width, height: s.height, rx: s.rx, ...props });
|
|
191
|
+
}
|
|
175
192
|
var STYLE_ID = "broberg-bodymap-styles";
|
|
176
193
|
var STYLE = `
|
|
177
194
|
.bmap{--bmap-accent:var(--primary,#0e8f8a);--bmap-line:#e2e8f0;--bmap-panel:#fff;--bmap-ink:#1e293b;--bmap-muted:#64748b;
|
|
178
195
|
display:flex;gap:18px;flex-wrap:wrap;align-items:flex-start;font:15px/1.5 ui-sans-serif,system-ui,-apple-system,sans-serif;color:var(--bmap-ink)}
|
|
179
|
-
.
|
|
180
|
-
.
|
|
181
|
-
.bmap__viewtoggle{display:inline-flex;
|
|
182
|
-
.bmap__vbtn{font:inherit;font-size:13px;font-weight:600;color:var(--bmap-muted);background:none;border:0;border-radius:7px;padding:
|
|
196
|
+
.bmap__stage{display:flex;flex-direction:column;gap:10px;flex:0 1 340px;min-width:0;max-width:360px}
|
|
197
|
+
.bmap__bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
|
|
198
|
+
.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}
|
|
183
200
|
.bmap__vbtn:hover{color:var(--bmap-ink)}
|
|
184
201
|
.bmap__vbtn:active{transform:scale(.97)}
|
|
185
202
|
.bmap__vbtn--on{background:#fff;color:var(--bmap-ink);box-shadow:0 1px 2px rgba(15,23,42,.14)}
|
|
186
|
-
.
|
|
187
|
-
.
|
|
188
|
-
.
|
|
189
|
-
.
|
|
190
|
-
.
|
|
191
|
-
.
|
|
203
|
+
.bmap__zoom{display:inline-flex;gap:4px;margin-left:auto}
|
|
204
|
+
.bmap__zoom button{font:inherit;font-weight:700;font-size:16px;line-height:1;width:36px;height:36px;border:1px solid var(--bmap-line);background:#fff;color:var(--bmap-ink);border-radius:8px;cursor:pointer;transition:border-color .12s,transform .1s;display:flex;align-items:center;justify-content:center}
|
|
205
|
+
.bmap__zoom button:hover{border-color:var(--bmap-accent)}
|
|
206
|
+
.bmap__zoom button:active{transform:scale(.9)}
|
|
207
|
+
.bmap__zoom button:disabled{opacity:.4;cursor:default}
|
|
208
|
+
.bmap__svg{width:100%;height:auto;display:block;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;background:transparent;border-radius:14px}
|
|
209
|
+
.bmap__vis{stroke:#fff;stroke-width:2.4;pointer-events:none;transition:filter .12s,stroke .12s,stroke-width .12s}
|
|
210
|
+
.bmap__vis--sel{stroke:var(--bmap-accent);stroke-width:3.6}
|
|
211
|
+
.bmap__hit{fill:transparent;cursor:pointer;outline:none}
|
|
212
|
+
.bmap__hit--locked{cursor:default}
|
|
213
|
+
.bmap__hit:hover + .bmap__vis{filter:brightness(1.08) saturate(1.15)}
|
|
214
|
+
.bmap__hit:focus-visible + .bmap__vis{stroke:var(--bmap-accent);stroke-width:4;stroke-dasharray:4 2.5}
|
|
215
|
+
.bmap__heatn{font:700 12px ui-sans-serif;fill:#fff;pointer-events:none}
|
|
216
|
+
.bmap__panel{flex:1 1 240px;min-width:220px;border:1px solid var(--bmap-line);border-radius:14px;padding:16px 18px;background:var(--bmap-panel)}
|
|
192
217
|
.bmap__panel--empty{color:var(--bmap-muted);font-size:13.5px}
|
|
193
218
|
.bmap__ph{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:12px}
|
|
194
219
|
.bmap__ph b{font-size:16px}
|
|
195
220
|
.bmap__code{font:11px ui-monospace,monospace;color:var(--bmap-muted);background:#f1f5f9;border-radius:6px;padding:2px 7px}
|
|
196
221
|
.bmap__lbl{font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--bmap-muted);margin:0 0 7px}
|
|
197
|
-
.bmap__scale{display:flex;gap:
|
|
198
|
-
.bmap__i{font:inherit;font-size:
|
|
222
|
+
.bmap__scale{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:14px}
|
|
223
|
+
.bmap__i{font:inherit;font-size:14px;font-weight:600;width:38px;height:38px;border-radius:9px;border:1px solid var(--bmap-line);background:#fff;color:var(--bmap-ink);cursor:pointer;transition:border-color .1s,background .1s,transform .1s}
|
|
199
224
|
.bmap__i:hover{border-color:var(--bmap-accent)}
|
|
200
225
|
.bmap__i:active{transform:scale(.9)}
|
|
201
226
|
.bmap__i--on{background:var(--bmap-accent);border-color:var(--bmap-accent);color:#fff}
|
|
202
|
-
.bmap__chips{display:flex;gap:
|
|
203
|
-
.bmap__chip{font:inherit;font-size:
|
|
227
|
+
.bmap__chips{display:flex;gap:7px;flex-wrap:wrap;margin-bottom:14px}
|
|
228
|
+
.bmap__chip{font:inherit;font-size:13px;border:1px solid var(--bmap-line);background:#fff;color:var(--bmap-muted);border-radius:999px;padding:9px 15px;cursor:pointer;transition:.1s}
|
|
204
229
|
.bmap__chip:hover{border-color:var(--bmap-accent);color:var(--bmap-accent)}
|
|
205
230
|
.bmap__chip:active{transform:scale(.96)}
|
|
206
231
|
.bmap__chip--on{background:var(--bmap-ink);border-color:var(--bmap-ink);color:#fff}
|
|
207
|
-
.bmap__rm{font:inherit;font-size:
|
|
232
|
+
.bmap__rm{font:inherit;font-size:14px;font-weight:600;border:1px solid #f6c9c9;background:#fff;color:#ef4444;border-radius:9px;padding:10px 15px;cursor:pointer;transition:.1s}
|
|
208
233
|
.bmap__rm:hover{background:#fef2f2}
|
|
209
234
|
.bmap__rm:active{transform:scale(.97)}
|
|
235
|
+
.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}
|
|
236
|
+
@media (max-width:560px){
|
|
237
|
+
.bmap{flex-direction:column;gap:14px}
|
|
238
|
+
.bmap__stage{max-width:100%;width:100%;flex-basis:auto}
|
|
239
|
+
.bmap__panel{width:100%}
|
|
240
|
+
.bmap__i{width:46px;height:46px;font-size:16px}
|
|
241
|
+
.bmap__chip{padding:11px 17px;font-size:15px}
|
|
242
|
+
.bmap__vbtn{padding:10px 18px;font-size:15px}
|
|
243
|
+
}
|
|
244
|
+
@media (prefers-reduced-motion:reduce){.bmap *{transition:none !important}}
|
|
210
245
|
`;
|
|
211
246
|
function ensureStyles() {
|
|
212
247
|
if (typeof document === "undefined") return;
|
|
@@ -216,6 +251,12 @@ function ensureStyles() {
|
|
|
216
251
|
el.textContent = STYLE;
|
|
217
252
|
document.head.appendChild(el);
|
|
218
253
|
}
|
|
254
|
+
var ZMIN = 1;
|
|
255
|
+
var ZMAX = 4;
|
|
256
|
+
var clampPan = (p, z2) => ({
|
|
257
|
+
x: Math.min(0, Math.max(VBW * (1 - z2), p.x)),
|
|
258
|
+
y: Math.min(0, Math.max(VBH * (1 - z2), p.y))
|
|
259
|
+
});
|
|
219
260
|
function BodyMap({
|
|
220
261
|
value,
|
|
221
262
|
defaultValue,
|
|
@@ -229,9 +270,81 @@ function BodyMap({
|
|
|
229
270
|
const [internal, setInternal] = useState(defaultValue ?? []);
|
|
230
271
|
const [selected, setSelected] = useState(null);
|
|
231
272
|
const [view, setView] = useState(defaultView);
|
|
273
|
+
const [zoom, setZoom] = useState(1);
|
|
274
|
+
const [pan, setPan] = useState({ x: 0, y: 0 });
|
|
232
275
|
const report = value ?? internal;
|
|
233
276
|
const shapes = view === "back" ? SHAPES_BACK : SHAPES;
|
|
234
277
|
const fills = view === "back" ? FILL_BACK : FILL;
|
|
278
|
+
const svgRef = useRef(null);
|
|
279
|
+
const zoomRef = useRef(1);
|
|
280
|
+
const panRef = useRef({ x: 0, y: 0 });
|
|
281
|
+
const pointers = useRef(/* @__PURE__ */ new Map());
|
|
282
|
+
const gesture = useRef({ startDist: 0, startZoom: 1, midVB: { x: 0, y: 0 }, start: { x: 0, y: 0 }, moved: false });
|
|
283
|
+
const suppressClick = useRef(false);
|
|
284
|
+
const setZP = (z2, p) => {
|
|
285
|
+
const cz = Math.max(ZMIN, Math.min(ZMAX, z2));
|
|
286
|
+
const cp = clampPan(p, cz);
|
|
287
|
+
zoomRef.current = cz;
|
|
288
|
+
panRef.current = cp;
|
|
289
|
+
setZoom(cz);
|
|
290
|
+
setPan(cp);
|
|
291
|
+
};
|
|
292
|
+
const screenToVB = (cx, cy) => {
|
|
293
|
+
const el = svgRef.current;
|
|
294
|
+
if (!el) return { x: VBW / 2, y: VBH / 2 };
|
|
295
|
+
const r = el.getBoundingClientRect();
|
|
296
|
+
if (!r.width || !r.height) return { x: VBW / 2, y: VBH / 2 };
|
|
297
|
+
return { x: (cx - r.left) / r.width * VBW, y: (cy - r.top) / r.height * VBH };
|
|
298
|
+
};
|
|
299
|
+
const zoomTo = (nz, focalVB) => {
|
|
300
|
+
const z2 = Math.max(ZMIN, Math.min(ZMAX, nz));
|
|
301
|
+
const p = panRef.current;
|
|
302
|
+
setZP(z2, { x: focalVB.x - (focalVB.x - p.x) * (z2 / zoomRef.current), y: focalVB.y - (focalVB.y - p.y) * (z2 / zoomRef.current) });
|
|
303
|
+
};
|
|
304
|
+
const zoomBtn = (factor) => zoomTo(zoomRef.current * factor, { x: VBW / 2, y: VBH / 2 });
|
|
305
|
+
const resetZoom = () => setZP(1, { x: 0, y: 0 });
|
|
306
|
+
const onPointerDown = (e) => {
|
|
307
|
+
pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
308
|
+
const pts = [...pointers.current.values()];
|
|
309
|
+
if (pts.length === 2) {
|
|
310
|
+
gesture.current.startDist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
|
311
|
+
gesture.current.startZoom = zoomRef.current;
|
|
312
|
+
gesture.current.midVB = screenToVB((pts[0].x + pts[1].x) / 2, (pts[0].y + pts[1].y) / 2);
|
|
313
|
+
gesture.current.moved = true;
|
|
314
|
+
suppressClick.current = true;
|
|
315
|
+
} else {
|
|
316
|
+
gesture.current.start = { x: e.clientX, y: e.clientY };
|
|
317
|
+
gesture.current.moved = false;
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
const onPointerMove = (e) => {
|
|
321
|
+
const prev = pointers.current.get(e.pointerId);
|
|
322
|
+
if (!prev) return;
|
|
323
|
+
pointers.current.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
|
324
|
+
const pts = [...pointers.current.values()];
|
|
325
|
+
if (pts.length >= 2) {
|
|
326
|
+
const dist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
|
327
|
+
if (gesture.current.startDist > 0) zoomTo(gesture.current.startZoom * (dist / gesture.current.startDist), gesture.current.midVB);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const el = svgRef.current;
|
|
331
|
+
const rect = el?.getBoundingClientRect();
|
|
332
|
+
if (Math.hypot(e.clientX - gesture.current.start.x, e.clientY - gesture.current.start.y) > 6) {
|
|
333
|
+
gesture.current.moved = true;
|
|
334
|
+
suppressClick.current = true;
|
|
335
|
+
}
|
|
336
|
+
if (zoomRef.current > 1 && rect && rect.width) {
|
|
337
|
+
const dxVB = (e.clientX - prev.x) / rect.width * VBW;
|
|
338
|
+
const dyVB = (e.clientY - prev.y) / rect.height * VBH;
|
|
339
|
+
setZP(zoomRef.current, { x: panRef.current.x + dxVB, y: panRef.current.y + dyVB });
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const onPointerUp = (e) => {
|
|
343
|
+
pointers.current.delete(e.pointerId);
|
|
344
|
+
if (pointers.current.size === 0) window.setTimeout(() => {
|
|
345
|
+
suppressClick.current = false;
|
|
346
|
+
}, 60);
|
|
347
|
+
};
|
|
235
348
|
const changeView = (v) => {
|
|
236
349
|
if (v === view) return;
|
|
237
350
|
setView(v);
|
|
@@ -252,68 +365,96 @@ function BodyMap({
|
|
|
252
365
|
commit(report.filter((p) => p.region !== key));
|
|
253
366
|
setSelected(null);
|
|
254
367
|
};
|
|
368
|
+
const pickRegion = (key) => {
|
|
369
|
+
if (suppressClick.current) {
|
|
370
|
+
suppressClick.current = false;
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
setSelected(key);
|
|
374
|
+
};
|
|
255
375
|
const regions = resolveRegions(config);
|
|
256
376
|
const region = selected ? REGIONS.find((r) => r.key === selected) : void 0;
|
|
257
377
|
const current = selected ? pointOf(selected) : void 0;
|
|
258
378
|
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;
|
|
382
|
+
};
|
|
259
383
|
return /* @__PURE__ */ jsxs("div", { className: rootCls, "data-testid": "bodymap-root", children: [
|
|
260
384
|
/* @__PURE__ */ jsxs("div", { className: "bmap__stage", children: [
|
|
261
|
-
/* @__PURE__ */ jsxs("div", { className: "
|
|
262
|
-
/* @__PURE__ */
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
385
|
+
/* @__PURE__ */ jsxs("div", { className: "bmap__bar", children: [
|
|
386
|
+
/* @__PURE__ */ jsxs("div", { className: "bmap__viewtoggle", role: "group", "aria-label": "Visning", children: [
|
|
387
|
+
/* @__PURE__ */ jsx(
|
|
388
|
+
"button",
|
|
389
|
+
{
|
|
390
|
+
type: "button",
|
|
391
|
+
className: "bmap__vbtn" + (view === "front" ? " bmap__vbtn--on" : ""),
|
|
392
|
+
"data-testid": "bodymap-view-front",
|
|
393
|
+
"aria-pressed": view === "front",
|
|
394
|
+
onClick: () => changeView("front"),
|
|
395
|
+
children: "Forfra"
|
|
396
|
+
}
|
|
397
|
+
),
|
|
398
|
+
/* @__PURE__ */ jsx(
|
|
399
|
+
"button",
|
|
400
|
+
{
|
|
401
|
+
type: "button",
|
|
402
|
+
className: "bmap__vbtn" + (view === "back" ? " bmap__vbtn--on" : ""),
|
|
403
|
+
"data-testid": "bodymap-view-back",
|
|
404
|
+
"aria-pressed": view === "back",
|
|
405
|
+
onClick: () => changeView("back"),
|
|
406
|
+
children: "Bagfra"
|
|
407
|
+
}
|
|
408
|
+
)
|
|
409
|
+
] }),
|
|
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: "+" })
|
|
414
|
+
] })
|
|
284
415
|
] }),
|
|
285
|
-
/* @__PURE__ */
|
|
416
|
+
/* @__PURE__ */ jsx(
|
|
286
417
|
"svg",
|
|
287
418
|
{
|
|
419
|
+
ref: svgRef,
|
|
288
420
|
className: "bmap__svg",
|
|
289
421
|
viewBox: "0 0 260 470",
|
|
290
|
-
width: 230,
|
|
291
|
-
height: 416,
|
|
292
422
|
role: "group",
|
|
293
423
|
"aria-label": "Kropskort \u2014 v\xE6lg hvor det g\xF8r ondt",
|
|
294
|
-
|
|
424
|
+
onPointerDown,
|
|
425
|
+
onPointerMove,
|
|
426
|
+
onPointerUp,
|
|
427
|
+
onPointerCancel: onPointerUp,
|
|
428
|
+
onWheel: (e) => zoomTo(zoomRef.current * (e.deltaY < 0 ? 1.12 : 1 / 1.12), screenToVB(e.clientX, e.clientY)),
|
|
429
|
+
children: /* @__PURE__ */ jsxs("g", { transform: `translate(${pan.x} ${pan.y}) scale(${zoom})`, children: [
|
|
295
430
|
regions.map((r) => {
|
|
296
431
|
const s = shapes[r.key];
|
|
297
432
|
if (!s) return null;
|
|
298
433
|
const marked = pointOf(r.key);
|
|
299
434
|
const selectable = isSelectable(r.key, config);
|
|
300
|
-
const
|
|
301
|
-
"bmap__region",
|
|
302
|
-
!selectable && "bmap__region--locked",
|
|
303
|
-
selected === r.key && "bmap__region--sel"
|
|
304
|
-
].filter(Boolean).join(" ");
|
|
435
|
+
const visCls = ["bmap__vis", selected === r.key && "bmap__vis--sel"].filter(Boolean).join(" ");
|
|
305
436
|
const fill = marked ? heat(marked.intensity) : fills[r.key] ?? "#cbd5e1";
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
437
|
+
const hs = hitShape(s);
|
|
438
|
+
const act = () => selectable ? pickRegion(r.key) : void 0;
|
|
439
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
440
|
+
shapeEl(hs, {
|
|
441
|
+
className: "bmap__hit" + (selectable ? "" : " bmap__hit--locked"),
|
|
442
|
+
"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) => {
|
|
449
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
450
|
+
e.preventDefault();
|
|
451
|
+
suppressClick.current = false;
|
|
452
|
+
setSelected(r.key);
|
|
453
|
+
}
|
|
454
|
+
} : void 0
|
|
455
|
+
}),
|
|
456
|
+
shapeEl(s, { className: visCls, fill, opacity: selectable ? 1 : 0.4 })
|
|
457
|
+
] }, r.key);
|
|
317
458
|
}),
|
|
318
459
|
report.map((p) => {
|
|
319
460
|
const s = shapes[p.region];
|
|
@@ -321,7 +462,7 @@ function BodyMap({
|
|
|
321
462
|
const c = center(s);
|
|
322
463
|
return /* @__PURE__ */ jsx("text", { x: c.x, y: c.y + 4, textAnchor: "middle", className: "bmap__heatn", children: p.intensity }, p.region);
|
|
323
464
|
})
|
|
324
|
-
]
|
|
465
|
+
] })
|
|
325
466
|
}
|
|
326
467
|
)
|
|
327
468
|
] }),
|
|
@@ -330,21 +471,22 @@ function BodyMap({
|
|
|
330
471
|
/* @__PURE__ */ jsx("b", { children: region.label }),
|
|
331
472
|
/* @__PURE__ */ jsx("span", { className: "bmap__code", children: region.code })
|
|
332
473
|
] }),
|
|
333
|
-
/* @__PURE__ */ jsx("p", { className: "bmap__lbl", children: "Intensitet (0-10)" }),
|
|
334
|
-
/* @__PURE__ */ jsx("div", { className: "bmap__scale", role: "group", "aria-
|
|
474
|
+
/* @__PURE__ */ jsx("p", { className: "bmap__lbl", id: "bmap-intensity-lbl", children: "Intensitet (0-10)" }),
|
|
475
|
+
/* @__PURE__ */ jsx("div", { className: "bmap__scale", role: "group", "aria-labelledby": "bmap-intensity-lbl", children: Array.from({ length: 11 }, (_, n) => /* @__PURE__ */ jsx(
|
|
335
476
|
"button",
|
|
336
477
|
{
|
|
337
478
|
type: "button",
|
|
338
479
|
className: "bmap__i" + (current?.intensity === n ? " bmap__i--on" : ""),
|
|
339
480
|
"data-testid": `bodymap-intensity-${n}`,
|
|
481
|
+
"aria-label": `Intensitet ${n}`,
|
|
340
482
|
"aria-pressed": current?.intensity === n,
|
|
341
483
|
onClick: () => setPain(region.key, n, current?.type),
|
|
342
484
|
children: n
|
|
343
485
|
},
|
|
344
486
|
n
|
|
345
487
|
)) }),
|
|
346
|
-
/* @__PURE__ */ jsx("p", { className: "bmap__lbl", children: "Kvalitet" }),
|
|
347
|
-
/* @__PURE__ */ jsx("div", { className: "bmap__chips", role: "group", "aria-
|
|
488
|
+
/* @__PURE__ */ jsx("p", { className: "bmap__lbl", id: "bmap-quality-lbl", children: "Kvalitet" }),
|
|
489
|
+
/* @__PURE__ */ jsx("div", { className: "bmap__chips", role: "group", "aria-labelledby": "bmap-quality-lbl", children: PAIN_TYPES.map((t) => /* @__PURE__ */ jsx(
|
|
348
490
|
"button",
|
|
349
491
|
{
|
|
350
492
|
type: "button",
|
|
@@ -357,7 +499,7 @@ function BodyMap({
|
|
|
357
499
|
t
|
|
358
500
|
)) }),
|
|
359
501
|
current && /* @__PURE__ */ jsx("button", { type: "button", className: "bmap__rm", "data-testid": "bodymap-remove", onClick: () => removePain(region.key), children: "Fjern punkt" })
|
|
360
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "bmap__panel bmap__panel--empty", "data-testid": "bodymap-panel", children: "
|
|
502
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "bmap__panel bmap__panel--empty", "data-testid": "bodymap-panel", children: "V\xE6lg en kropsdel for at markere smerte." })
|
|
361
503
|
] });
|
|
362
504
|
}
|
|
363
505
|
|