@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/dist/three.d.cts
CHANGED
|
@@ -404,6 +404,18 @@ declare const INTENSITY_SIZE = 46;
|
|
|
404
404
|
* takes exactly this. INTENSITY_SIZE sits above it.
|
|
405
405
|
*/
|
|
406
406
|
declare const TOUCH_MIN = 44;
|
|
407
|
+
/**
|
|
408
|
+
* F052.35 — whether fullscreen must STACK the panel under the stage instead of
|
|
409
|
+
* beside it. The row lays the canvas (basis 520) and the panel (basis 300) side
|
|
410
|
+
* by side with an 18px gap and wraps when they do not fit — and in fullscreen
|
|
411
|
+
* the canvas is the full height, so a wrapped panel lands entirely below the
|
|
412
|
+
* screen. Measured on iphone-15 (369px of content): every intensity button and
|
|
413
|
+
* the close sat at top >= innerHeight, so a patient could mark a body part and
|
|
414
|
+
* not say how much it hurt. Stacking shrinks the stage instead, and the panel
|
|
415
|
+
* stays on screen. Pure so the threshold is asserted without a browser.
|
|
416
|
+
*/
|
|
417
|
+
declare const FS_ROW_MIN: number;
|
|
418
|
+
declare function stackInFullscreen(contentWidth: number): boolean;
|
|
407
419
|
/**
|
|
408
420
|
* F052.33 — should the help note be on screen at all?
|
|
409
421
|
*
|
|
@@ -424,4 +436,4 @@ declare function shouldShowHint(opts: {
|
|
|
424
436
|
}): boolean;
|
|
425
437
|
declare function BodyMap3D(props: BodyMap3DProps): react.JSX.Element;
|
|
426
438
|
|
|
427
|
-
export { BodyMap3D, type BodyMap3DHint, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, INTENSITY_SIZE, TAP_MAX_DISTANCE, TOUCH_MIN, ensureNormals, isTap, seamBlend, seamWeight, serializeReport, shouldShowHint };
|
|
439
|
+
export { BodyMap3D, type BodyMap3DHint, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, FS_ROW_MIN, INTENSITY_SIZE, TAP_MAX_DISTANCE, TOUCH_MIN, ensureNormals, isTap, seamBlend, seamWeight, serializeReport, shouldShowHint, stackInFullscreen };
|
package/dist/three.d.ts
CHANGED
|
@@ -404,6 +404,18 @@ declare const INTENSITY_SIZE = 46;
|
|
|
404
404
|
* takes exactly this. INTENSITY_SIZE sits above it.
|
|
405
405
|
*/
|
|
406
406
|
declare const TOUCH_MIN = 44;
|
|
407
|
+
/**
|
|
408
|
+
* F052.35 — whether fullscreen must STACK the panel under the stage instead of
|
|
409
|
+
* beside it. The row lays the canvas (basis 520) and the panel (basis 300) side
|
|
410
|
+
* by side with an 18px gap and wraps when they do not fit — and in fullscreen
|
|
411
|
+
* the canvas is the full height, so a wrapped panel lands entirely below the
|
|
412
|
+
* screen. Measured on iphone-15 (369px of content): every intensity button and
|
|
413
|
+
* the close sat at top >= innerHeight, so a patient could mark a body part and
|
|
414
|
+
* not say how much it hurt. Stacking shrinks the stage instead, and the panel
|
|
415
|
+
* stays on screen. Pure so the threshold is asserted without a browser.
|
|
416
|
+
*/
|
|
417
|
+
declare const FS_ROW_MIN: number;
|
|
418
|
+
declare function stackInFullscreen(contentWidth: number): boolean;
|
|
407
419
|
/**
|
|
408
420
|
* F052.33 — should the help note be on screen at all?
|
|
409
421
|
*
|
|
@@ -424,4 +436,4 @@ declare function shouldShowHint(opts: {
|
|
|
424
436
|
}): boolean;
|
|
425
437
|
declare function BodyMap3D(props: BodyMap3DProps): react.JSX.Element;
|
|
426
438
|
|
|
427
|
-
export { BodyMap3D, type BodyMap3DHint, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, INTENSITY_SIZE, TAP_MAX_DISTANCE, TOUCH_MIN, ensureNormals, isTap, seamBlend, seamWeight, serializeReport, shouldShowHint };
|
|
439
|
+
export { BodyMap3D, type BodyMap3DHint, type BodyMap3DModels, type BodyMap3DProps, type BodyMap3DSex, type BodyMap3DUiLabels, FS_ROW_MIN, INTENSITY_SIZE, TAP_MAX_DISTANCE, TOUCH_MIN, ensureNormals, isTap, seamBlend, seamWeight, serializeReport, shouldShowHint, stackInFullscreen };
|
package/dist/three.js
CHANGED
|
@@ -291,6 +291,10 @@ function webglAvailable() {
|
|
|
291
291
|
}
|
|
292
292
|
var INTENSITY_SIZE = 46;
|
|
293
293
|
var TOUCH_MIN = 44;
|
|
294
|
+
var FS_ROW_MIN = 520 + 18 + 300;
|
|
295
|
+
function stackInFullscreen(contentWidth) {
|
|
296
|
+
return contentWidth < FS_ROW_MIN;
|
|
297
|
+
}
|
|
294
298
|
function shouldShowHint(opts) {
|
|
295
299
|
if (opts.openRegion) return false;
|
|
296
300
|
if (opts.hint === false) return false;
|
|
@@ -383,6 +387,17 @@ function BodyMap3D(props) {
|
|
|
383
387
|
onFullscreenChange?.(v);
|
|
384
388
|
};
|
|
385
389
|
const apiRef = useRef(null);
|
|
390
|
+
const rowRef = useRef(null);
|
|
391
|
+
const [rowWidth, setRowWidth] = useState(0);
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
const el = rowRef.current;
|
|
394
|
+
if (!isFullscreen || !el) return;
|
|
395
|
+
setRowWidth(el.clientWidth);
|
|
396
|
+
const ro = typeof ResizeObserver !== "undefined" ? new ResizeObserver(() => setRowWidth(el.clientWidth)) : null;
|
|
397
|
+
ro?.observe(el);
|
|
398
|
+
return () => ro?.disconnect();
|
|
399
|
+
}, [isFullscreen]);
|
|
400
|
+
const stacked = isFullscreen && stackInFullscreen(rowWidth);
|
|
386
401
|
useEffect(() => {
|
|
387
402
|
if (!webglAvailable()) {
|
|
388
403
|
setUnsupported(true);
|
|
@@ -761,70 +776,90 @@ function BodyMap3D(props) {
|
|
|
761
776
|
children: isFullscreen ? "\xD7" : UI.expand ?? "Expand"
|
|
762
777
|
}
|
|
763
778
|
) }),
|
|
764
|
-
/* @__PURE__ */ jsxs(
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
height: isFullscreen ? "100%" : canvasH,
|
|
783
|
-
minWidth: 320,
|
|
784
|
-
minHeight: isFullscreen ? 0 : void 0,
|
|
785
|
-
borderRadius: 16,
|
|
786
|
-
overflow: "hidden",
|
|
787
|
-
background: chrome.stageBg,
|
|
788
|
-
touchAction: "none"
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
),
|
|
792
|
-
/* @__PURE__ */ jsx("div", { style: { flex: "1 1 300px", minWidth: 260 }, children: region ? /* @__PURE__ */ jsxs("div", { style: { border: `1px solid ${chrome.border}`, borderRadius: 14, padding: 16, background: chrome.panelBg }, children: [
|
|
793
|
-
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
794
|
-
/* @__PURE__ */ jsx("b", { style: { fontSize: 16 }, children: nameOf(region.key) }),
|
|
795
|
-
showRegionCode && /* @__PURE__ */ 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: [
|
|
796
|
-
region.code,
|
|
797
|
-
region.side ? " \xB7 " + region.side : ""
|
|
798
|
-
] }),
|
|
799
|
-
/* @__PURE__ */ jsx(
|
|
800
|
-
"button",
|
|
779
|
+
/* @__PURE__ */ jsxs(
|
|
780
|
+
"div",
|
|
781
|
+
{
|
|
782
|
+
ref: rowRef,
|
|
783
|
+
"data-layout": stacked ? "stacked" : void 0,
|
|
784
|
+
style: {
|
|
785
|
+
display: "flex",
|
|
786
|
+
gap: 18,
|
|
787
|
+
alignItems: "flex-start",
|
|
788
|
+
flexWrap: "wrap",
|
|
789
|
+
...isFullscreen ? { flex: "1 1 auto", minHeight: 0 } : null,
|
|
790
|
+
// F052.35 — a column that never wraps: the stage takes what the panel
|
|
791
|
+
// leaves, so the panel is always inside the screen.
|
|
792
|
+
...stacked ? { flexDirection: "column", flexWrap: "nowrap", alignItems: "stretch", gap: 12 } : null
|
|
793
|
+
},
|
|
794
|
+
children: [
|
|
795
|
+
unsupported ? /* @__PURE__ */ jsx(
|
|
796
|
+
"div",
|
|
801
797
|
{
|
|
802
|
-
|
|
803
|
-
"
|
|
804
|
-
|
|
805
|
-
onClick: () => setSelected(null),
|
|
806
|
-
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 },
|
|
807
|
-
children: "\xD7"
|
|
798
|
+
"data-testid": "bodymap3d-unsupported",
|
|
799
|
+
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 },
|
|
800
|
+
children: "3D kr\xE6ver WebGL, som ikke er tilg\xE6ngeligt her."
|
|
808
801
|
}
|
|
809
|
-
)
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
802
|
+
) : /* @__PURE__ */ jsx(
|
|
803
|
+
"div",
|
|
804
|
+
{
|
|
805
|
+
ref: mountRef,
|
|
806
|
+
"data-testid": "bodymap3d-canvas",
|
|
807
|
+
style: {
|
|
808
|
+
flex: "1 1 520px",
|
|
809
|
+
// In fullscreen the box takes the height it is GIVEN rather than a
|
|
810
|
+
// fixed `canvasHeight`; the ResizeObserver above is what makes the
|
|
811
|
+
// picture follow it.
|
|
812
|
+
height: isFullscreen ? "100%" : canvasH,
|
|
813
|
+
minWidth: 320,
|
|
814
|
+
minHeight: isFullscreen ? 0 : void 0,
|
|
815
|
+
// Stacked: the stage fills what is left above the panel, and
|
|
816
|
+
// keeps a floor so the body never disappears behind it.
|
|
817
|
+
...stacked ? { flex: "1 1 0", height: "auto", minWidth: 0, minHeight: 160 } : null,
|
|
818
|
+
borderRadius: 16,
|
|
819
|
+
overflow: "hidden",
|
|
820
|
+
background: chrome.stageBg,
|
|
821
|
+
touchAction: "none"
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
),
|
|
825
|
+
/* @__PURE__ */ 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__ */ jsxs("div", { style: { border: `1px solid ${chrome.border}`, borderRadius: 14, padding: 16, background: chrome.panelBg }, children: [
|
|
826
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }, children: [
|
|
827
|
+
/* @__PURE__ */ jsx("b", { style: { fontSize: 16 }, children: nameOf(region.key) }),
|
|
828
|
+
showRegionCode && /* @__PURE__ */ 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: [
|
|
829
|
+
region.code,
|
|
830
|
+
region.side ? " \xB7 " + region.side : ""
|
|
831
|
+
] }),
|
|
832
|
+
/* @__PURE__ */ jsx(
|
|
833
|
+
"button",
|
|
834
|
+
{
|
|
835
|
+
type: "button",
|
|
836
|
+
"data-testid": "bodymap3d-close",
|
|
837
|
+
"aria-label": L.close,
|
|
838
|
+
onClick: () => setSelected(null),
|
|
839
|
+
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 },
|
|
840
|
+
children: "\xD7"
|
|
841
|
+
}
|
|
842
|
+
)
|
|
843
|
+
] }),
|
|
844
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.intensity }),
|
|
845
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, marginBottom: 12 }, children: Array.from({ length: 11 }, (_, i) => (
|
|
846
|
+
/* F052.34 — CIRCLES, and all eleven the same size.
|
|
847
|
+
Dimensioned by «10», the widest label: sizing by the average
|
|
848
|
+
would make the two-digit one an ellipse among circles, which
|
|
849
|
+
is worse than eleven rounded squares because the odd one out
|
|
850
|
+
becomes a different SHAPE rather than slightly wider.
|
|
851
|
+
46px is the SIBLING's touch size, not a new number: the 2D
|
|
852
|
+
renderer already used it and this one was on 30px, under the
|
|
853
|
+
>=44px floor F052.8 set. See INTENSITY_SIZE. */
|
|
854
|
+
/* @__PURE__ */ 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)
|
|
855
|
+
)) }),
|
|
856
|
+
/* @__PURE__ */ jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.quality }),
|
|
857
|
+
/* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 14 }, children: PAIN_TYPES.map((t) => /* @__PURE__ */ 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)) }),
|
|
858
|
+
current && /* @__PURE__ */ jsx("button", { "data-testid": "bodymap3d-remove", onClick: () => removePain(region.key), style: { ...btn(chrome), color: chrome.danger, borderColor: chrome.dangerBorder }, children: L.remove })
|
|
859
|
+
] }) : showEmptyHint && !hintAtStage ? /* @__PURE__ */ 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 })
|
|
860
|
+
]
|
|
861
|
+
}
|
|
862
|
+
),
|
|
828
863
|
showEmptyHint && hintAtStage && /* F052.31 — pinned INSIDE the fullscreen surface, not in the column that
|
|
829
864
|
wraps beneath a full-height canvas. That wrap is what put the note at
|
|
830
865
|
858-912 in an 852px window on a real iPhone: entirely offscreen, and
|
|
@@ -855,6 +890,6 @@ function BodyMap3D(props) {
|
|
|
855
890
|
);
|
|
856
891
|
}
|
|
857
892
|
|
|
858
|
-
export { BodyMap3D, INTENSITY_SIZE, TAP_MAX_DISTANCE, TOUCH_MIN, ensureNormals, isTap, seamBlend, seamWeight, serializeReport, shouldShowHint };
|
|
893
|
+
export { BodyMap3D, FS_ROW_MIN, INTENSITY_SIZE, TAP_MAX_DISTANCE, TOUCH_MIN, ensureNormals, isTap, seamBlend, seamWeight, serializeReport, shouldShowHint, stackInFullscreen };
|
|
859
894
|
//# sourceMappingURL=three.js.map
|
|
860
895
|
//# sourceMappingURL=three.js.map
|