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