@broberg/bodymap 0.8.0 → 0.10.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/dist/three.cjs CHANGED
@@ -111,7 +111,16 @@ var defaultUi = {
111
111
  stageBg: STAGE_BG,
112
112
  border: "#e2e8f0",
113
113
  badgeBg: "#f1f5f9",
114
- danger: "#dc2626"
114
+ danger: "#dc2626",
115
+ dangerBorder: "#f6c9c9",
116
+ // F052.30 — was #0e8f8a, which measured 3.95:1 against white. AA needs 4.5,
117
+ // and this is the SELECTED intensity button on a surface where AA is a legal
118
+ // duty. It shipped from 0.2.0 and no check could see it: the colour was an
119
+ // inline literal, and the F052.19 contrast sweep only read this object.
120
+ // #0c7d77 is the same teal a shade deeper — 4.98:1, with headroom rather than
121
+ // sitting on the 4.5 boundary where any later tweak reopens it.
122
+ accent: "#0c7d77",
123
+ accentText: "#fff"
115
124
  };
116
125
  function uiColors(palette) {
117
126
  return { ...defaultUi, ...palette?.ui ?? {} };
@@ -225,8 +234,8 @@ var LABELS_EN = {
225
234
  zoomOut: "Zoom out",
226
235
  zoomReset: "Reset zoom"
227
236
  };
228
- 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" };
229
- var UI_EN = { male: "Male", female: "Female", hoverHint: "Hover to highlight \xB7 tap a body part to mark pain.", expand: "Expand", collapse: "Close" };
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" };
230
239
  var ANCHORS = {
231
240
  head: [0, 1.79, 0.02],
232
241
  neck: [0, 1.57, 0],
@@ -298,8 +307,20 @@ function webglAvailable() {
298
307
  return false;
299
308
  }
300
309
  }
301
- var btn = { font: "inherit", cursor: "pointer", borderRadius: 8, border: "1px solid #e2e8f0", background: "#fff", padding: "6px 9px" };
302
- var seg = (on) => ({ ...btn, background: on ? "#0e8f8a" : "#fff", color: on ? "#fff" : "#1e293b", fontWeight: 600 });
310
+ var btn = (c) => ({
311
+ font: "inherit",
312
+ cursor: "pointer",
313
+ borderRadius: 8,
314
+ border: `1px solid ${c.border}`,
315
+ background: c.panelBg,
316
+ padding: "6px 9px"
317
+ });
318
+ var seg = (c, on) => ({
319
+ ...btn(c),
320
+ background: on ? c.accent : c.panelBg,
321
+ color: on ? c.accentText : c.text,
322
+ fontWeight: 600
323
+ });
303
324
  function BodyMap3D(props) {
304
325
  const {
305
326
  models,
@@ -321,6 +342,7 @@ function BodyMap3D(props) {
321
342
  onFeedback,
322
343
  haptics,
323
344
  seam = false,
345
+ hint,
324
346
  fullscreen: fullscreenProp,
325
347
  onFullscreenChange,
326
348
  showFullscreenButton = true,
@@ -680,7 +702,9 @@ function BodyMap3D(props) {
680
702
  const region = selected ? REGIONS.find((r) => r.key === selected) : null;
681
703
  const current = selected ? pointOf(selected) : void 0;
682
704
  const anySelectable = REGIONS.some((r) => isSelectable(r.key, config ?? {}));
683
- const showEmptyHint = anySelectable || ui?.hoverHint !== void 0;
705
+ const showEmptyHint = hint === false ? false : anySelectable || ui?.hoverHint !== void 0;
706
+ const hintText = (hint ? hint.text : void 0) ?? UI.hoverHint;
707
+ const hintAtStage = isFullscreen && !!hint && hint.placement === "stage-bottom";
684
708
  return /* @__PURE__ */ jsxRuntime.jsxs(
685
709
  "div",
686
710
  {
@@ -689,7 +713,7 @@ function BodyMap3D(props) {
689
713
  "data-fullscreen": isFullscreen ? "true" : void 0,
690
714
  style: {
691
715
  fontFamily: "system-ui, sans-serif",
692
- color: "#1e293b",
716
+ color: chrome.text,
693
717
  // A viewport fill, not the Fullscreen API — see the `fullscreen` prop.
694
718
  // `fixed`+`inset:0` behaves the same on iOS Safari, where element
695
719
  // fullscreen does not exist at all, as it does everywhere else.
@@ -721,8 +745,8 @@ function BodyMap3D(props) {
721
745
  },
722
746
  children: [
723
747
  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: [
724
- /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-sex-male", onClick: () => changeSex("male"), style: seg(sex === "male"), children: UI.male }),
725
- /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-sex-female", onClick: () => changeSex("female"), style: seg(sex === "female"), children: UI.female })
748
+ /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-sex-male", onClick: () => changeSex("male"), style: seg(chrome, sex === "male"), children: UI.male }),
749
+ /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-sex-female", onClick: () => changeSex("female"), style: seg(chrome, sex === "female"), children: UI.female })
726
750
  ] }) }),
727
751
  /* @__PURE__ */ jsxRuntime.jsx("span", { ref: loadedRef, "data-testid": "bodymap3d-loaded", style: { display: "none" } }),
728
752
  ready && /* @__PURE__ */ jsxRuntime.jsx("span", { "data-testid": "bodymap3d-ready", style: { position: "absolute", width: 1, height: 1, opacity: 0, pointerEvents: "none" } }),
@@ -735,7 +759,7 @@ function BodyMap3D(props) {
735
759
  "aria-label": isFullscreen ? UI.collapse ?? "Close" : UI.expand ?? "Expand",
736
760
  onClick: () => setFullscreen(!isFullscreen),
737
761
  style: {
738
- ...btn,
762
+ ...btn(chrome),
739
763
  color: chrome.text,
740
764
  borderColor: chrome.border,
741
765
  background: chrome.panelBg,
@@ -786,18 +810,43 @@ function BodyMap3D(props) {
786
810
  "data-testid": "bodymap3d-close",
787
811
  "aria-label": L.close,
788
812
  onClick: () => setSelected(null),
789
- style: { ...btn, width: 32, height: 32, padding: 0, fontSize: 20, lineHeight: 1, color: chrome.mutedText, borderColor: chrome.border, background: chrome.panelBg },
813
+ style: { ...btn(chrome), width: 32, height: 32, padding: 0, fontSize: 20, lineHeight: 1, color: chrome.mutedText, borderColor: chrome.border, background: chrome.panelBg },
790
814
  children: "\xD7"
791
815
  }
792
816
  )
793
817
  ] }),
794
818
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.intensity }),
795
- /* @__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)) }),
819
+ /* @__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(chrome), display: "inline-flex", alignItems: "center", justifyContent: "center", minWidth: 30, height: 30, padding: 0, background: current?.intensity === i ? chrome.accent : chrome.panelBg, color: current?.intensity === i ? chrome.accentText : chrome.text }, children: i }, i)) }),
796
820
  /* @__PURE__ */ jsxRuntime.jsx("div", { style: { fontSize: 11, fontWeight: 700, letterSpacing: ".06em", textTransform: "uppercase", color: chrome.mutedText, marginBottom: 7 }, children: L.quality }),
797
- /* @__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)) }),
798
- current && /* @__PURE__ */ jsxRuntime.jsx("button", { "data-testid": "bodymap3d-remove", onClick: () => removePain(region.key), style: { ...btn, color: chrome.danger, borderColor: "#f6c9c9" }, children: L.remove })
799
- ] }) : 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 })
800
- ] })
821
+ /* @__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)) }),
822
+ 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 })
823
+ ] }) : 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 })
824
+ ] }),
825
+ showEmptyHint && hintAtStage && /* F052.31 — pinned INSIDE the fullscreen surface, not in the column that
826
+ wraps beneath a full-height canvas. That wrap is what put the note at
827
+ 858-912 in an 852px window on a real iPhone: entirely offscreen, and
828
+ it reads as a bug in the consumer's app rather than in ours.
829
+ The bottom inset clears the home indicator, exactly as the exit
830
+ control's does (F052.27). */
831
+ /* @__PURE__ */ jsxRuntime.jsx(
832
+ "div",
833
+ {
834
+ "data-testid": "bodymap3d-empty",
835
+ "data-placement": "stage-bottom",
836
+ style: {
837
+ flex: "0 0 auto",
838
+ marginTop: 12,
839
+ marginBottom: "env(safe-area-inset-bottom)",
840
+ border: `1px solid ${chrome.border}`,
841
+ borderRadius: 14,
842
+ padding: 16,
843
+ background: chrome.panelBg,
844
+ color: chrome.mutedText,
845
+ fontSize: 13.5
846
+ },
847
+ children: hintText
848
+ }
849
+ )
801
850
  ]
802
851
  }
803
852
  );