@broberg/bodymap 0.5.0 → 0.7.0
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/README.md +64 -0
- package/dist/react.cjs +18 -1
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +2 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +18 -1
- package/dist/react.js.map +1 -1
- package/dist/three.cjs +118 -27
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +19 -0
- package/dist/three.d.ts +19 -0
- package/dist/three.js +118 -27
- package/dist/three.js.map +1 -1
- package/package.json +1 -1
package/dist/three.cjs
CHANGED
|
@@ -183,6 +183,7 @@ var LABELS_DA = {
|
|
|
183
183
|
intensity: "Intensitet (0-10)",
|
|
184
184
|
quality: "Kvalitet",
|
|
185
185
|
remove: "Fjern punkt",
|
|
186
|
+
close: "Luk",
|
|
186
187
|
front: "Forfra",
|
|
187
188
|
back: "Bagfra",
|
|
188
189
|
empty: "V\xE6lg en kropsdel for at markere smerte.",
|
|
@@ -205,6 +206,7 @@ var LABELS_EN = {
|
|
|
205
206
|
intensity: "Intensity (0-10)",
|
|
206
207
|
quality: "Quality",
|
|
207
208
|
remove: "Remove point",
|
|
209
|
+
close: "Close",
|
|
208
210
|
front: "Front",
|
|
209
211
|
back: "Back",
|
|
210
212
|
empty: "Pick a body part to mark pain.",
|
|
@@ -221,8 +223,8 @@ var LABELS_EN = {
|
|
|
221
223
|
zoomOut: "Zoom out",
|
|
222
224
|
zoomReset: "Reset zoom"
|
|
223
225
|
};
|
|
224
|
-
var UI_DA = { male: "Mand", female: "Kvinde", hoverHint: "Hover for at fremh\xE6ve \xB7 klik en kropsdel for at markere smerte." };
|
|
225
|
-
var UI_EN = { male: "Male", female: "Female", hoverHint: "Hover to highlight \xB7 tap a body part to mark pain." };
|
|
226
|
+
var UI_DA = { male: "Mand", female: "Kvinde", hoverHint: "Hover for at fremh\xE6ve \xB7 klik en kropsdel for at markere smerte.", expand: "Vis stor", collapse: "Luk stor visning" };
|
|
227
|
+
var UI_EN = { male: "Male", female: "Female", hoverHint: "Hover to highlight \xB7 tap a body part to mark pain.", expand: "Expand", collapse: "Close" };
|
|
226
228
|
var ANCHORS = {
|
|
227
229
|
head: [0, 1.79, 0.02],
|
|
228
230
|
neck: [0, 1.57, 0],
|
|
@@ -309,6 +311,9 @@ function BodyMap3D(props) {
|
|
|
309
311
|
onFeedback,
|
|
310
312
|
haptics,
|
|
311
313
|
seam = false,
|
|
314
|
+
fullscreen: fullscreenProp,
|
|
315
|
+
onFullscreenChange,
|
|
316
|
+
showFullscreenButton = true,
|
|
312
317
|
className
|
|
313
318
|
} = props;
|
|
314
319
|
const chrome = uiColors(palette);
|
|
@@ -348,6 +353,12 @@ function BodyMap3D(props) {
|
|
|
348
353
|
setReadyRef.current = setReady;
|
|
349
354
|
const seamRef = react.useRef(seam);
|
|
350
355
|
seamRef.current = seam;
|
|
356
|
+
const [internalFs, setInternalFs] = react.useState(false);
|
|
357
|
+
const isFullscreen = fullscreenProp ?? internalFs;
|
|
358
|
+
const setFullscreen = (v) => {
|
|
359
|
+
if (fullscreenProp === void 0) setInternalFs(v);
|
|
360
|
+
onFullscreenChange?.(v);
|
|
361
|
+
};
|
|
351
362
|
const apiRef = react.useRef(null);
|
|
352
363
|
react.useEffect(() => {
|
|
353
364
|
if (!webglAvailable()) {
|
|
@@ -597,8 +608,11 @@ function BodyMap3D(props) {
|
|
|
597
608
|
renderFrame();
|
|
598
609
|
};
|
|
599
610
|
window.addEventListener("resize", onResize);
|
|
611
|
+
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => onResize()) : null;
|
|
612
|
+
ro?.observe(el);
|
|
600
613
|
return () => {
|
|
601
614
|
cancelAnimationFrame(raf);
|
|
615
|
+
ro?.disconnect();
|
|
602
616
|
window.removeEventListener("resize", onResize);
|
|
603
617
|
document.removeEventListener("visibilitychange", onVis);
|
|
604
618
|
controls.removeEventListener("change", kick);
|
|
@@ -610,6 +624,16 @@ function BodyMap3D(props) {
|
|
|
610
624
|
if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
|
|
611
625
|
};
|
|
612
626
|
}, []);
|
|
627
|
+
react.useEffect(() => {
|
|
628
|
+
if (!isFullscreen && !selected) return;
|
|
629
|
+
const onKey = (e) => {
|
|
630
|
+
if (e.key !== "Escape") return;
|
|
631
|
+
if (selected) setSelected(null);
|
|
632
|
+
else setFullscreen(false);
|
|
633
|
+
};
|
|
634
|
+
window.addEventListener("keydown", onKey);
|
|
635
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
636
|
+
});
|
|
613
637
|
react.useEffect(() => {
|
|
614
638
|
apiRef.current?.refresh();
|
|
615
639
|
}, [selected, report, palette, seam]);
|
|
@@ -644,31 +668,98 @@ function BodyMap3D(props) {
|
|
|
644
668
|
const current = selected ? pointOf(selected) : void 0;
|
|
645
669
|
const anySelectable = REGIONS.some((r) => isSelectable(r.key, config ?? {}));
|
|
646
670
|
const showEmptyHint = anySelectable || ui?.hoverHint !== void 0;
|
|
647
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
671
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
672
|
+
"div",
|
|
673
|
+
{
|
|
674
|
+
"data-testid": "bodymap3d-root",
|
|
675
|
+
className,
|
|
676
|
+
"data-fullscreen": isFullscreen ? "true" : void 0,
|
|
677
|
+
style: {
|
|
678
|
+
fontFamily: "system-ui, sans-serif",
|
|
679
|
+
color: "#1e293b",
|
|
680
|
+
// A viewport fill, not the Fullscreen API — see the `fullscreen` prop.
|
|
681
|
+
// `fixed`+`inset:0` behaves the same on iOS Safari, where element
|
|
682
|
+
// fullscreen does not exist at all, as it does everywhere else.
|
|
683
|
+
...isFullscreen ? {
|
|
684
|
+
position: "fixed",
|
|
685
|
+
inset: 0,
|
|
686
|
+
zIndex: 2147483e3,
|
|
687
|
+
background: chrome.panelBg,
|
|
688
|
+
padding: 12,
|
|
689
|
+
display: "flex",
|
|
690
|
+
flexDirection: "column",
|
|
691
|
+
overflow: "auto"
|
|
692
|
+
} : null
|
|
693
|
+
},
|
|
694
|
+
children: [
|
|
695
|
+
showSexToggle && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", gap: 16, flexWrap: "wrap", marginBottom: 12, fontSize: 13 }, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 6, alignItems: "center" }, children: [
|
|
696
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-sex-male", onClick: () => changeSex("male"), style: seg(sex === "male"), children: UI.male }),
|
|
697
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-sex-female", onClick: () => changeSex("female"), style: seg(sex === "female"), children: UI.female })
|
|
698
|
+
] }) }),
|
|
699
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { ref: loadedRef, "data-testid": "bodymap3d-loaded", style: { display: "none" } }),
|
|
700
|
+
ready && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "bodymap3d-ready", style: { position: "absolute", width: 1, height: 1, opacity: 0, pointerEvents: "none" } }),
|
|
701
|
+
showFullscreenButton && !unsupported && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", justifyContent: "flex-end", marginBottom: 8 }, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
702
|
+
"button",
|
|
703
|
+
{
|
|
704
|
+
type: "button",
|
|
705
|
+
"data-testid": "bodymap3d-fullscreen",
|
|
706
|
+
"aria-pressed": isFullscreen,
|
|
707
|
+
"aria-label": isFullscreen ? UI.collapse ?? "Close" : UI.expand ?? "Expand",
|
|
708
|
+
onClick: () => setFullscreen(!isFullscreen),
|
|
709
|
+
style: { ...btn, color: chrome.text, borderColor: chrome.border, background: chrome.panelBg },
|
|
710
|
+
children: isFullscreen ? UI.collapse ?? "Close" : UI.expand ?? "Expand"
|
|
711
|
+
}
|
|
712
|
+
) }),
|
|
713
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 18, alignItems: "flex-start", flexWrap: "wrap", ...isFullscreen ? { flex: "1 1 auto", minHeight: 0 } : null }, children: [
|
|
714
|
+
unsupported ? /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "bodymap3d-unsupported", style: { flex: "1 1 520px", minWidth: 320, height: canvasH, borderRadius: 16, background: "#0e1424", color: "#cbd5e1", display: "flex", alignItems: "center", justifyContent: "center", padding: 24, textAlign: "center", fontSize: 14 }, children: "3D kr\xE6ver WebGL, som ikke er tilg\xE6ngeligt her." }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
715
|
+
"div",
|
|
716
|
+
{
|
|
717
|
+
ref: mountRef,
|
|
718
|
+
"data-testid": "bodymap3d-canvas",
|
|
719
|
+
style: {
|
|
720
|
+
flex: "1 1 520px",
|
|
721
|
+
// In fullscreen the box takes the height it is GIVEN rather than a
|
|
722
|
+
// fixed `canvasHeight`; the ResizeObserver above is what makes the
|
|
723
|
+
// picture follow it.
|
|
724
|
+
height: isFullscreen ? "100%" : canvasH,
|
|
725
|
+
minWidth: 320,
|
|
726
|
+
minHeight: isFullscreen ? 0 : void 0,
|
|
727
|
+
borderRadius: 16,
|
|
728
|
+
overflow: "hidden",
|
|
729
|
+
background: "#0e1424",
|
|
730
|
+
touchAction: "none"
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
),
|
|
734
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { flex: "1 1 300px", minWidth: 260 }, children: region ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: `1px solid ${chrome.border}`, borderRadius: 14, padding: 16, background: chrome.panelBg }, children: [
|
|
735
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
736
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { style: { fontSize: 16 }, children: nameOf(region.key) }),
|
|
737
|
+
showRegionCode && /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-testid": "bodymap3d-region-code", style: { font: "11px ui-monospace, monospace", color: chrome.mutedText, background: chrome.badgeBg, borderRadius: 6, padding: "2px 7px" }, children: [
|
|
738
|
+
region.code,
|
|
739
|
+
region.side ? " \xB7 " + region.side : ""
|
|
740
|
+
] }),
|
|
741
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
742
|
+
"button",
|
|
743
|
+
{
|
|
744
|
+
type: "button",
|
|
745
|
+
"data-testid": "bodymap3d-close",
|
|
746
|
+
"aria-label": L.close,
|
|
747
|
+
onClick: () => setSelected(null),
|
|
748
|
+
style: { ...btn, width: 32, height: 32, padding: 0, fontSize: 20, lineHeight: 1, color: chrome.mutedText, borderColor: chrome.border, background: chrome.panelBg },
|
|
749
|
+
children: "\xD7"
|
|
750
|
+
}
|
|
751
|
+
)
|
|
752
|
+
] }),
|
|
753
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.intensity }),
|
|
754
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 12 }, children: Array.from({ length: 11 }, (_, i) => /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": `bodymap3d-intensity-${i}`, onClick: () => setPain(region.key, i, current?.type), style: { ...btn, display: "inline-flex", alignItems: "center", justifyContent: "center", minWidth: 30, height: 30, padding: 0, background: current?.intensity === i ? "#0e8f8a" : "#fff", color: current?.intensity === i ? "#fff" : "#1e293b" }, children: i }, i)) }),
|
|
755
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.quality }),
|
|
756
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 14 }, children: PAIN_TYPES.map((t) => /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": `bodymap3d-type-${t}`, onClick: () => setPain(region.key, current?.intensity ?? 5, t), style: { ...btn, borderRadius: 999, padding: "6px 12px", background: current?.type === t ? "#1e293b" : "#fff", color: current?.type === t ? "#fff" : "#64748b" }, children: L.qualities[t] ?? t }, t)) }),
|
|
757
|
+
current && /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-remove", onClick: () => removePain(region.key), style: { ...btn, color: chrome.danger, borderColor: "#f6c9c9" }, children: L.remove })
|
|
758
|
+
] }) : showEmptyHint ? /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "bodymap3d-empty", style: { border: `1px solid ${chrome.border}`, borderRadius: 14, padding: 16, background: chrome.panelBg, color: chrome.mutedText, fontSize: 13.5 }, children: UI.hoverHint }) : null })
|
|
759
|
+
] })
|
|
760
|
+
]
|
|
761
|
+
}
|
|
762
|
+
);
|
|
672
763
|
}
|
|
673
764
|
|
|
674
765
|
exports.BodyMap3D = BodyMap3D;
|