@broberg/bodymap 0.9.0 → 0.11.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 +45 -0
- package/dist/three.cjs +41 -8
- package/dist/three.cjs.map +1 -1
- package/dist/three.d.cts +49 -1
- package/dist/three.d.ts +49 -1
- package/dist/three.js +40 -9
- package/dist/three.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -382,6 +382,51 @@ is invisible on a desktop monitor and obvious on a phone.
|
|
|
382
382
|
|
|
383
383
|
3D only — the 2D renderer has no canvas and no fullscreen, so it ignores it.
|
|
384
384
|
|
|
385
|
+
### A slow tap is still a tap (0.11.0)
|
|
386
|
+
|
|
387
|
+
Until 0.11.0 a press held longer than **450 ms** was discarded — silently. No
|
|
388
|
+
pick, no clear, no `onFeedback`, no repaint.
|
|
389
|
+
|
|
390
|
+
Distance already separates a tap from a drag: a drag moves. The time ceiling did
|
|
391
|
+
not help with that; what it did was rule that a **slow** press is not a tap. And a
|
|
392
|
+
slow press is exactly what a **careful** tap is — aiming at a small body part on a
|
|
393
|
+
phone, at a mark you are trying to hit again, takes longer than a casual poke.
|
|
394
|
+
|
|
395
|
+
The owner reported it twice, as *"the remove button is gone"* and as *"the sound
|
|
396
|
+
doesn't work"*. Both were the same swallowed tap, and each was explained away
|
|
397
|
+
separately. **A tap that is silently discarded is indistinguishable from a broken
|
|
398
|
+
component.**
|
|
399
|
+
|
|
400
|
+
`isTap(dx, dy)` is exported if you need the same rule.
|
|
401
|
+
|
|
402
|
+
### `hint` — turn the help note off, or move it (0.10.0)
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
<BodyMap3D hint={false} /> // don't show it
|
|
406
|
+
<BodyMap3D hint={{ text: "…", placement: "stage-bottom" }} /> // yours, pinned in the stage
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
**`hint={false}` is the "do not show this" that did not exist.** `ui.hoverHint`
|
|
410
|
+
only ever changed the *text*, and setting it to `""` produced an empty bordered
|
|
411
|
+
box — worse than the sentence. `ui.hoverHint` still works and is not deprecated;
|
|
412
|
+
`hint.text` is simply the newer way to say the same thing.
|
|
413
|
+
|
|
414
|
+
**`placement: "stage-bottom"` fixes a real clipping bug rather than moving it.**
|
|
415
|
+
The note sits in normal flow in the panel column. In fullscreen the canvas takes
|
|
416
|
+
the full height, that column wraps beneath it, and the note lands past the bottom
|
|
417
|
+
edge — measured on a real iPhone at **858–912 in an 852px window**, entirely
|
|
418
|
+
offscreen. `stage-bottom` pins it inside the fullscreen surface, clear of the home
|
|
419
|
+
indicator. Outside fullscreen it stays where it was, because there the flow
|
|
420
|
+
position is right.
|
|
421
|
+
|
|
422
|
+
You cannot make that call in CSS without knowing our internal structure, which is
|
|
423
|
+
why the prop exists.
|
|
424
|
+
|
|
425
|
+
⚠️ **The default text changed in 0.10.0.** It said *"Hover to highlight"* —
|
|
426
|
+
describing an interaction that does not exist on a touchscreen, on a component
|
|
427
|
+
whose primary surface is a phone. Now *"Drag to rotate · tap a body part to mark
|
|
428
|
+
pain."*
|
|
429
|
+
|
|
385
430
|
### `ui.accent` — the controls read your palette now (0.9.0)
|
|
386
431
|
|
|
387
432
|
Until 0.9.0 the buttons carried their colours inline, so a consumer palette
|
package/dist/three.cjs
CHANGED
|
@@ -234,8 +234,8 @@ var LABELS_EN = {
|
|
|
234
234
|
zoomOut: "Zoom out",
|
|
235
235
|
zoomReset: "Reset zoom"
|
|
236
236
|
};
|
|
237
|
-
var UI_DA = { male: "Mand", female: "Kvinde", hoverHint: "
|
|
238
|
-
var UI_EN = { male: "Male", female: "Female", hoverHint: "
|
|
237
|
+
var UI_DA = { male: "Mand", female: "Kvinde", hoverHint: "Tr\xE6k for at dreje \xB7 tryk en kropsdel for at markere smerte.", expand: "Vis stor", collapse: "Luk stor visning" };
|
|
238
|
+
var UI_EN = { male: "Male", female: "Female", hoverHint: "Drag to rotate \xB7 tap a body part to mark pain.", expand: "Expand", collapse: "Close" };
|
|
239
239
|
var ANCHORS = {
|
|
240
240
|
head: [0, 1.79, 0.02],
|
|
241
241
|
neck: [0, 1.57, 0],
|
|
@@ -282,6 +282,10 @@ function seamBlend(nearestSq, secondSq) {
|
|
|
282
282
|
function seamWeight(seamEnabled, blend) {
|
|
283
283
|
return seamEnabled ? blend : 0;
|
|
284
284
|
}
|
|
285
|
+
function isTap(dx, dy, maxDistance = TAP_MAX_DISTANCE) {
|
|
286
|
+
return Math.hypot(dx, dy) <= maxDistance;
|
|
287
|
+
}
|
|
288
|
+
var TAP_MAX_DISTANCE = 6;
|
|
285
289
|
function ensureNormals(geo) {
|
|
286
290
|
if (geo.getAttribute("normal")) return false;
|
|
287
291
|
geo.computeVertexNormals();
|
|
@@ -342,6 +346,7 @@ function BodyMap3D(props) {
|
|
|
342
346
|
onFeedback,
|
|
343
347
|
haptics,
|
|
344
348
|
seam = false,
|
|
349
|
+
hint,
|
|
345
350
|
fullscreen: fullscreenProp,
|
|
346
351
|
onFullscreenChange,
|
|
347
352
|
showFullscreenButton = true,
|
|
@@ -594,12 +599,11 @@ function BodyMap3D(props) {
|
|
|
594
599
|
}
|
|
595
600
|
return best;
|
|
596
601
|
};
|
|
597
|
-
let downX = 0, downY = 0
|
|
602
|
+
let downX = 0, downY = 0;
|
|
598
603
|
const canvas = renderer.domElement;
|
|
599
604
|
const onDown = (e) => {
|
|
600
605
|
downX = e.clientX;
|
|
601
606
|
downY = e.clientY;
|
|
602
|
-
downT = Date.now();
|
|
603
607
|
};
|
|
604
608
|
const onMove = (e) => {
|
|
605
609
|
if ((e.buttons || 0) !== 0) return;
|
|
@@ -611,7 +615,7 @@ function BodyMap3D(props) {
|
|
|
611
615
|
renderFrame();
|
|
612
616
|
};
|
|
613
617
|
const onUp = (e) => {
|
|
614
|
-
if (
|
|
618
|
+
if (!isTap(e.clientX - downX, e.clientY - downY)) return;
|
|
615
619
|
const k = pick(e.clientX, e.clientY);
|
|
616
620
|
if (!k) return;
|
|
617
621
|
const outcome = pickRef.current(k);
|
|
@@ -701,7 +705,9 @@ function BodyMap3D(props) {
|
|
|
701
705
|
const region = selected ? REGIONS.find((r) => r.key === selected) : null;
|
|
702
706
|
const current = selected ? pointOf(selected) : void 0;
|
|
703
707
|
const anySelectable = REGIONS.some((r) => isSelectable(r.key, config ?? {}));
|
|
704
|
-
const showEmptyHint = anySelectable || ui?.hoverHint !== void 0;
|
|
708
|
+
const showEmptyHint = hint === false ? false : anySelectable || ui?.hoverHint !== void 0;
|
|
709
|
+
const hintText = (hint ? hint.text : void 0) ?? UI.hoverHint;
|
|
710
|
+
const hintAtStage = isFullscreen && !!hint && hint.placement === "stage-bottom";
|
|
705
711
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
706
712
|
"div",
|
|
707
713
|
{
|
|
@@ -817,15 +823,42 @@ function BodyMap3D(props) {
|
|
|
817
823
|
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.quality }),
|
|
818
824
|
/* @__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)) }),
|
|
819
825
|
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 })
|
|
820
|
-
] }) : 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:
|
|
821
|
-
] })
|
|
826
|
+
] }) : 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 })
|
|
827
|
+
] }),
|
|
828
|
+
showEmptyHint && hintAtStage && /* F052.31 — pinned INSIDE the fullscreen surface, not in the column that
|
|
829
|
+
wraps beneath a full-height canvas. That wrap is what put the note at
|
|
830
|
+
858-912 in an 852px window on a real iPhone: entirely offscreen, and
|
|
831
|
+
it reads as a bug in the consumer's app rather than in ours.
|
|
832
|
+
The bottom inset clears the home indicator, exactly as the exit
|
|
833
|
+
control's does (F052.27). */
|
|
834
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
835
|
+
"div",
|
|
836
|
+
{
|
|
837
|
+
"data-testid": "bodymap3d-empty",
|
|
838
|
+
"data-placement": "stage-bottom",
|
|
839
|
+
style: {
|
|
840
|
+
flex: "0 0 auto",
|
|
841
|
+
marginTop: 12,
|
|
842
|
+
marginBottom: "env(safe-area-inset-bottom)",
|
|
843
|
+
border: `1px solid ${chrome.border}`,
|
|
844
|
+
borderRadius: 14,
|
|
845
|
+
padding: 16,
|
|
846
|
+
background: chrome.panelBg,
|
|
847
|
+
color: chrome.mutedText,
|
|
848
|
+
fontSize: 13.5
|
|
849
|
+
},
|
|
850
|
+
children: hintText
|
|
851
|
+
}
|
|
852
|
+
)
|
|
822
853
|
]
|
|
823
854
|
}
|
|
824
855
|
);
|
|
825
856
|
}
|
|
826
857
|
|
|
827
858
|
exports.BodyMap3D = BodyMap3D;
|
|
859
|
+
exports.TAP_MAX_DISTANCE = TAP_MAX_DISTANCE;
|
|
828
860
|
exports.ensureNormals = ensureNormals;
|
|
861
|
+
exports.isTap = isTap;
|
|
829
862
|
exports.seamBlend = seamBlend;
|
|
830
863
|
exports.seamWeight = seamWeight;
|
|
831
864
|
exports.serializeReport = serializeReport;
|