@broberg/bodymap 0.13.3 → 0.14.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 +8 -0
- package/dist/three.cjs +99 -62
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +13 -1
- package/dist/three.d.ts +13 -1
- package/dist/three.js +98 -63
- package/dist/three.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -274,6 +274,14 @@ this component's primary surface. A `position: fixed; inset: 0` overlay behaves
|
|
|
274
274
|
identically on every platform, needs no user-gesture dance, and cannot leave you
|
|
275
275
|
with a control that silently does nothing on the one device that matters most.
|
|
276
276
|
|
|
277
|
+
**On a narrow screen the panel goes UNDER the body (0.14.0).** Side by side needs
|
|
278
|
+
`FS_ROW_MIN` (838px) of content width. Below that, fullscreen stacks: the body
|
|
279
|
+
shrinks to what the panel leaves (never under 160px) and the panel sits on screen
|
|
280
|
+
beneath it. Before 0.14.0 the panel wrapped below a full-height canvas — on an
|
|
281
|
+
iPhone every intensity button sat under the bottom edge, so a patient could mark
|
|
282
|
+
a body part and not say how much it hurt. Wider than 838px, nothing changed.
|
|
283
|
+
`stackInFullscreen(width)` is exported if you need the same decision.
|
|
284
|
+
|
|
277
285
|
### It ships with a `ResizeObserver`, and that is not incidental
|
|
278
286
|
|
|
279
287
|
Before 0.6.0 the canvas measured the element **once at mount** and only re-measured
|
package/dist/three.cjs
CHANGED
|
@@ -313,6 +313,10 @@ function webglAvailable() {
|
|
|
313
313
|
}
|
|
314
314
|
var INTENSITY_SIZE = 46;
|
|
315
315
|
var TOUCH_MIN = 44;
|
|
316
|
+
var FS_ROW_MIN = 520 + 18 + 300;
|
|
317
|
+
function stackInFullscreen(contentWidth) {
|
|
318
|
+
return contentWidth < FS_ROW_MIN;
|
|
319
|
+
}
|
|
316
320
|
function shouldShowHint(opts) {
|
|
317
321
|
if (opts.openRegion) return false;
|
|
318
322
|
if (opts.hint === false) return false;
|
|
@@ -405,6 +409,17 @@ function BodyMap3D(props) {
|
|
|
405
409
|
onFullscreenChange?.(v);
|
|
406
410
|
};
|
|
407
411
|
const apiRef = react.useRef(null);
|
|
412
|
+
const rowRef = react.useRef(null);
|
|
413
|
+
const [rowWidth, setRowWidth] = react.useState(0);
|
|
414
|
+
react.useEffect(() => {
|
|
415
|
+
const el = rowRef.current;
|
|
416
|
+
if (!isFullscreen || !el) return;
|
|
417
|
+
setRowWidth(el.clientWidth);
|
|
418
|
+
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => setRowWidth(el.clientWidth)) : null;
|
|
419
|
+
ro?.observe(el);
|
|
420
|
+
return () => ro?.disconnect();
|
|
421
|
+
}, [isFullscreen]);
|
|
422
|
+
const stacked = isFullscreen && stackInFullscreen(rowWidth);
|
|
408
423
|
react.useEffect(() => {
|
|
409
424
|
if (!webglAvailable()) {
|
|
410
425
|
setUnsupported(true);
|
|
@@ -783,70 +798,90 @@ function BodyMap3D(props) {
|
|
|
783
798
|
children: isFullscreen ? "\xD7" : UI.expand ?? "Expand"
|
|
784
799
|
}
|
|
785
800
|
) }),
|
|
786
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
height: isFullscreen ? "100%" : canvasH,
|
|
805
|
-
minWidth: 320,
|
|
806
|
-
minHeight: isFullscreen ? 0 : void 0,
|
|
807
|
-
borderRadius: 16,
|
|
808
|
-
overflow: "hidden",
|
|
809
|
-
background: chrome.stageBg,
|
|
810
|
-
touchAction: "none"
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
),
|
|
814
|
-
/* @__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: [
|
|
815
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
816
|
-
/* @__PURE__ */ jsxRuntime.jsx("b", { style: { fontSize: 16 }, children: nameOf(region.key) }),
|
|
817
|
-
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: [
|
|
818
|
-
region.code,
|
|
819
|
-
region.side ? " \xB7 " + region.side : ""
|
|
820
|
-
] }),
|
|
821
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
822
|
-
"button",
|
|
801
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
802
|
+
"div",
|
|
803
|
+
{
|
|
804
|
+
ref: rowRef,
|
|
805
|
+
"data-layout": stacked ? "stacked" : void 0,
|
|
806
|
+
style: {
|
|
807
|
+
display: "flex",
|
|
808
|
+
gap: 18,
|
|
809
|
+
alignItems: "flex-start",
|
|
810
|
+
flexWrap: "wrap",
|
|
811
|
+
...isFullscreen ? { flex: "1 1 auto", minHeight: 0 } : null,
|
|
812
|
+
// F052.35 — a column that never wraps: the stage takes what the panel
|
|
813
|
+
// leaves, so the panel is always inside the screen.
|
|
814
|
+
...stacked ? { flexDirection: "column", flexWrap: "nowrap", alignItems: "stretch", gap: 12 } : null
|
|
815
|
+
},
|
|
816
|
+
children: [
|
|
817
|
+
unsupported ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
818
|
+
"div",
|
|
823
819
|
{
|
|
824
|
-
|
|
825
|
-
"
|
|
826
|
-
|
|
827
|
-
onClick: () => setSelected(null),
|
|
828
|
-
style: { ...btn(chrome), width: TOUCH_MIN, height: TOUCH_MIN, minWidth: TOUCH_MIN, padding: 0, borderRadius: "50%", fontSize: 20, lineHeight: 1, color: chrome.mutedText, borderColor: chrome.border, background: chrome.panelBg },
|
|
829
|
-
children: "\xD7"
|
|
820
|
+
"data-testid": "bodymap3d-unsupported",
|
|
821
|
+
style: { flex: "1 1 520px", minWidth: 320, height: canvasH, borderRadius: 16, background: chrome.panelBg, color: chrome.mutedText, border: `1px solid ${chrome.border}`, display: "flex", alignItems: "center", justifyContent: "center", padding: 24, textAlign: "center", fontSize: 14 },
|
|
822
|
+
children: "3D kr\xE6ver WebGL, som ikke er tilg\xE6ngeligt her."
|
|
830
823
|
}
|
|
831
|
-
)
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
824
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
825
|
+
"div",
|
|
826
|
+
{
|
|
827
|
+
ref: mountRef,
|
|
828
|
+
"data-testid": "bodymap3d-canvas",
|
|
829
|
+
style: {
|
|
830
|
+
flex: "1 1 520px",
|
|
831
|
+
// In fullscreen the box takes the height it is GIVEN rather than a
|
|
832
|
+
// fixed `canvasHeight`; the ResizeObserver above is what makes the
|
|
833
|
+
// picture follow it.
|
|
834
|
+
height: isFullscreen ? "100%" : canvasH,
|
|
835
|
+
minWidth: 320,
|
|
836
|
+
minHeight: isFullscreen ? 0 : void 0,
|
|
837
|
+
// Stacked: the stage fills what is left above the panel, and
|
|
838
|
+
// keeps a floor so the body never disappears behind it.
|
|
839
|
+
...stacked ? { flex: "1 1 0", height: "auto", minWidth: 0, minHeight: 160 } : null,
|
|
840
|
+
borderRadius: 16,
|
|
841
|
+
overflow: "hidden",
|
|
842
|
+
background: chrome.stageBg,
|
|
843
|
+
touchAction: "none"
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
),
|
|
847
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "bodymap3d-panel-col", style: { flex: "1 1 300px", minWidth: 260, ...stacked ? { flex: "0 1 auto", minWidth: 0, minHeight: 0, overflowY: "auto" } : null }, children: region ? /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { border: `1px solid ${chrome.border}`, borderRadius: 14, padding: 16, background: chrome.panelBg }, children: [
|
|
848
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
849
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { style: { fontSize: 16 }, children: nameOf(region.key) }),
|
|
850
|
+
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: [
|
|
851
|
+
region.code,
|
|
852
|
+
region.side ? " \xB7 " + region.side : ""
|
|
853
|
+
] }),
|
|
854
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
855
|
+
"button",
|
|
856
|
+
{
|
|
857
|
+
type: "button",
|
|
858
|
+
"data-testid": "bodymap3d-close",
|
|
859
|
+
"aria-label": L.close,
|
|
860
|
+
onClick: () => setSelected(null),
|
|
861
|
+
style: { ...btn(chrome), width: TOUCH_MIN, height: TOUCH_MIN, minWidth: TOUCH_MIN, padding: 0, borderRadius: "50%", fontSize: 20, lineHeight: 1, color: chrome.mutedText, borderColor: chrome.border, background: chrome.panelBg },
|
|
862
|
+
children: "\xD7"
|
|
863
|
+
}
|
|
864
|
+
)
|
|
865
|
+
] }),
|
|
866
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.intensity }),
|
|
867
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 12 }, children: Array.from({ length: 11 }, (_, i) => (
|
|
868
|
+
/* F052.34 — CIRCLES, and all eleven the same size.
|
|
869
|
+
Dimensioned by «10», the widest label: sizing by the average
|
|
870
|
+
would make the two-digit one an ellipse among circles, which
|
|
871
|
+
is worse than eleven rounded squares because the odd one out
|
|
872
|
+
becomes a different SHAPE rather than slightly wider.
|
|
873
|
+
46px is the SIBLING's touch size, not a new number: the 2D
|
|
874
|
+
renderer already used it and this one was on 30px, under the
|
|
875
|
+
>=44px floor F052.8 set. See INTENSITY_SIZE. */
|
|
876
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": `bodymap3d-intensity-${i}`, onClick: () => setPain(region.key, i, current?.type), style: { ...btn(chrome), display: "inline-flex", alignItems: "center", justifyContent: "center", width: INTENSITY_SIZE, height: INTENSITY_SIZE, minWidth: INTENSITY_SIZE, padding: 0, borderRadius: "50%", background: current?.intensity === i ? chrome.accent : chrome.panelBg, color: current?.intensity === i ? chrome.accentText : chrome.text }, children: i }, i)
|
|
877
|
+
)) }),
|
|
878
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.quality }),
|
|
879
|
+
/* @__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(chrome), borderRadius: 999, padding: "6px 12px", background: current?.type === t ? chrome.text : chrome.panelBg, color: current?.type === t ? chrome.panelBg : chrome.mutedText }, children: L.qualities[t] ?? t }, t)) }),
|
|
880
|
+
current && /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-remove", onClick: () => removePain(region.key), style: { ...btn(chrome), color: chrome.danger, borderColor: chrome.dangerBorder }, children: L.remove })
|
|
881
|
+
] }) : showEmptyHint && !hintAtStage ? /* @__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: hintText }) : null })
|
|
882
|
+
]
|
|
883
|
+
}
|
|
884
|
+
),
|
|
850
885
|
showEmptyHint && hintAtStage && /* F052.31 — pinned INSIDE the fullscreen surface, not in the column that
|
|
851
886
|
wraps beneath a full-height canvas. That wrap is what put the note at
|
|
852
887
|
858-912 in an 852px window on a real iPhone: entirely offscreen, and
|
|
@@ -878,6 +913,7 @@ function BodyMap3D(props) {
|
|
|
878
913
|
}
|
|
879
914
|
|
|
880
915
|
exports.BodyMap3D = BodyMap3D;
|
|
916
|
+
exports.FS_ROW_MIN = FS_ROW_MIN;
|
|
881
917
|
exports.INTENSITY_SIZE = INTENSITY_SIZE;
|
|
882
918
|
exports.TAP_MAX_DISTANCE = TAP_MAX_DISTANCE;
|
|
883
919
|
exports.TOUCH_MIN = TOUCH_MIN;
|
|
@@ -887,5 +923,6 @@ exports.seamBlend = seamBlend;
|
|
|
887
923
|
exports.seamWeight = seamWeight;
|
|
888
924
|
exports.serializeReport = serializeReport;
|
|
889
925
|
exports.shouldShowHint = shouldShowHint;
|
|
926
|
+
exports.stackInFullscreen = stackInFullscreen;
|
|
890
927
|
//# sourceMappingURL=three.cjs.map
|
|
891
928
|
//# sourceMappingURL=three.cjs.map
|