react-native-body-parts-anatomy 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Eslam Elfateh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,183 @@
1
+ # react-native-body-parts-anatomy
2
+
3
+ Tappable, zoomable, individually-addressable human body diagram for React Native — 308 SVG
4
+ fragments across male/female, front/back, zero native code.
5
+
6
+ ## Features
7
+
8
+ - **308 individually-tappable fragments** (vs. ~35 merged regions in other body-map libraries) —
9
+ each muscle group is split into its real anatomical sub-pieces (e.g. `abs` is 8 separate
10
+ fragments per gender/view), each with an auto-derived `side` (`left`/`right`/`null`) and `axis`
11
+ (`upper`/`middle`/`lower`/`inner`/`center`/`outer`/`null`) label.
12
+ - Male/female × front/back — 4 view combinations.
13
+ - Pinch-to-zoom, pan-while-zoomed, and step +/− buttons, all in one component.
14
+ - Two modes: **interactive picker** (`onFragmentPress`) or **read-only diagram**
15
+ (`zoomable={false}`, colored via `colorForSlug`).
16
+ - **Zero native code** — pure JS/TSX, only existing community peers
17
+ (`react-native-svg`, `react-native-gesture-handler`, `react-native-reanimated`).
18
+ - Fully themeable via props — no bundled theme system, no i18n framework assumed.
19
+ - Honors the OS Reduce Motion setting automatically.
20
+
21
+ ## Installation
22
+
23
+ ```sh
24
+ npm install react-native-body-parts-anatomy
25
+ npx expo install react-native-svg react-native-gesture-handler react-native-reanimated
26
+ ```
27
+
28
+ ### Required setup
29
+
30
+ **Wrap your app root in `GestureHandlerRootView`.** The zoomable mode uses a `GestureDetector`
31
+ for pinch/pan, which throws at runtime without it:
32
+
33
+ ```tsx
34
+ import { GestureHandlerRootView } from 'react-native-gesture-handler';
35
+
36
+ export default function App() {
37
+ return (
38
+ <GestureHandlerRootView style={{ flex: 1 }}>
39
+ {/* ...your app... */}
40
+ </GestureHandlerRootView>
41
+ );
42
+ }
43
+ ```
44
+
45
+ You can skip this only if you exclusively use `zoomable={false}`.
46
+
47
+ On the bare React Native CLI (not Expo), also follow each peer's own native setup —
48
+ notably `react-native-reanimated`'s Babel plugin. Those are the peers' setup steps, not this
49
+ package's.
50
+
51
+ > **Reanimated v4 users:** Reanimated 4 moved worklets into a separate `react-native-worklets`
52
+ > package and requires the New Architecture. This package never imports `react-native-worklets`
53
+ > directly, so it isn't declared as a peer here — but you must satisfy *Reanimated's* own peer
54
+ > requirement, and keep a **single** `react-native-worklets` version in your tree. A mismatch
55
+ > between the installed JS version and the Babel plugin version fails at startup with
56
+ > `[Worklets] Mismatch between JavaScript code version and Worklets Babel plugin version`.
57
+
58
+ ## Quick start
59
+
60
+ ### Interactive picker
61
+
62
+ ```tsx
63
+ import { useState } from 'react';
64
+ import { BodySilhouette } from 'react-native-body-parts-anatomy';
65
+
66
+ function PainAreaPicker() {
67
+ const [selectedSlugs, setSelectedSlugs] = useState<string[]>([]);
68
+
69
+ const toggleFragment = (slug: string) => {
70
+ setSelectedSlugs((current) =>
71
+ current.includes(slug) ? current.filter((item) => item !== slug) : [...current, slug]
72
+ );
73
+ };
74
+
75
+ return (
76
+ <BodySilhouette
77
+ gender="male"
78
+ view="front"
79
+ selectedSlugs={selectedSlugs}
80
+ onFragmentPress={toggleFragment}
81
+ />
82
+ );
83
+ }
84
+ ```
85
+
86
+ ### Read-only diagram
87
+
88
+ ```tsx
89
+ import { BodySilhouette } from 'react-native-body-parts-anatomy';
90
+
91
+ function SymptomDiagram({ colorBySlug }: { colorBySlug: Map<string, string> }) {
92
+ return (
93
+ <BodySilhouette
94
+ gender="female"
95
+ view="back"
96
+ zoomable={false}
97
+ selectedSlugs={Array.from(colorBySlug.keys())}
98
+ colorForSlug={(slug) => colorBySlug.get(slug)}
99
+ />
100
+ );
101
+ }
102
+ ```
103
+
104
+ ## Props
105
+
106
+ | Prop | Type | Default | Description |
107
+ |---|---|---|---|
108
+ | `gender` | `'male' \| 'female'` | — (required) | |
109
+ | `view` | `'front' \| 'back'` | — (required) | |
110
+ | `selectedSlugs` | `string[]` | — (required) | Fragment slugs currently highlighted. |
111
+ | `colorForSlug` | `(slug: string) => string \| undefined` | — | Per-slug color override for selected fragments; falls back to `selectedFragmentColor` when it returns `undefined`. |
112
+ | `onFragmentPress` | `(slug: string) => void` | — | Omit for a read-only diagram. |
113
+ | `zoomable` | `boolean` | `true` | Enables pinch/pan/step-zoom and the floating +/− buttons. |
114
+ | `outlineColor` | `string` | `'#333333'` | Skin-outline stroke color. |
115
+ | `unselectedFragmentColor` | `string` | `'#D6D6D6'` | Fill for fragments not in `selectedSlugs`. |
116
+ | `selectedFragmentColor` | `string` | `'#2563EB'` | Fallback fill when `colorForSlug` is omitted or returns `undefined`. |
117
+ | `zoomButtonBackgroundColor` | `string` | `'#FFFFFF'` | |
118
+ | `zoomButtonBorderColor` | `string` | `'#D6D6D6'` | |
119
+ | `zoomButtonIconColor` | `string` | `'#333333'` | |
120
+ | `zoomInAccessibilityLabel` | `string` | `'Zoom in'` | |
121
+ | `zoomOutAccessibilityLabel` | `string` | `'Zoom out'` | |
122
+ | `minScale` | `number` | `1` | |
123
+ | `maxScale` | `number` | `3` | |
124
+ | `zoomStep` | `number` | `0.5` | Scale delta per tap of a zoom button. |
125
+ | `zoomAnimationDurationMs` | `number` | `200` | |
126
+
127
+ ## Exported data & types
128
+
129
+ ```ts
130
+ import { BODY_REGIONS, FRAGMENTS_BY_PARENT, FRAGMENT_BY_SLUG } from 'react-native-body-parts-anatomy';
131
+ import type { BodyFragment, BodyRegionSet } from 'react-native-body-parts-anatomy';
132
+
133
+ // The full fragment tree, if you need to iterate it yourself.
134
+ BODY_REGIONS.male.front.fragments; // BodyFragment[]
135
+
136
+ // Reverse lookup: parent muscle name -> every fragment slug under it
137
+ // (e.g. legacy-data migration, or building a "select whole muscle" affordance).
138
+ FRAGMENTS_BY_PARENT['abs']; // ["abs-male-front-1", "abs-female-front-1", ...]
139
+
140
+ // O(1) lookup by fragment slug — useful for building your own label text.
141
+ const fragment = FRAGMENT_BY_SLUG['abs-male-front-3'];
142
+ // { slug, parentSlug: 'abs', side: 'left', axis: 'upper', pathData }
143
+ const label = [fragment.parentSlug, fragment.side, fragment.axis].filter(Boolean).join(' · ');
144
+ ```
145
+
146
+ ## Regenerating the data (maintainers only)
147
+
148
+ `src/data/bodyRegions.generated.ts` is generated, not hand-written, by
149
+ `scripts/generate-body-regions.mjs`, which reads `react-native-body-highlighter`'s raw SVG path
150
+ data. This script is repo-only — it is **not** part of the published package (see the `files`
151
+ allowlist in `package.json`) and pulls in `svgpath` as a devDependency only it uses.
152
+
153
+ ```sh
154
+ npm run generate-data
155
+ ```
156
+
157
+ This temporarily installs `react-native-body-highlighter@3.2.0` (`--no-save`), runs the codegen,
158
+ then leaves your `package.json` untouched. See `THIRD_PARTY_NOTICES.md` for why this package
159
+ depends on that library's data at build time but never at runtime.
160
+
161
+ ## Known limitations
162
+
163
+ - **`head` and `hair` are excluded** from the fragment data (not relevant to the pain-tracking use
164
+ case this was originally built for). Re-including them is a one-line change to
165
+ `EXCLUDED_SLUGS` in `scripts/generate-body-regions.mjs`, followed by regenerating and
166
+ re-verifying the data — not done for this initial release.
167
+
168
+ ## License & attribution
169
+
170
+ MIT — see [LICENSE](./LICENSE).
171
+
172
+ The fragment path data is derived from `react-native-body-highlighter` (MIT, ELABBASSI Hicham) —
173
+ see [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md) for the full attribution.
174
+
175
+ ## Contributing
176
+
177
+ - [Development workflow](CONTRIBUTING.md#development-workflow)
178
+ - [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
179
+ - [Code of conduct](CODE_OF_CONDUCT.md)
180
+
181
+ ---
182
+
183
+ Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
@@ -0,0 +1,41 @@
1
+ # Third-Party Notices
2
+
3
+ This package's anatomical path data (`src/data/bodyRegions.generated.ts`) is derived from
4
+ **react-native-body-highlighter** by ELABBASSI Hicham (npm: `HichamELBSI`), an MIT-licensed
5
+ package.
6
+
7
+ Specifically: this package's SVG fragment paths and skin-outline paths are extracted, split into
8
+ finer-grained addressable pieces, and re-serialized from `react-native-body-highlighter`'s
9
+ bundled SVG path data (see `scripts/generate-body-regions.mjs` for the extraction process). No
10
+ code from `react-native-body-highlighter` is executed at runtime by this package — it is only a
11
+ build-time input, temporarily installed as a dev dependency when regenerating the data.
12
+
13
+ Because the resulting path data is a derivative of `react-native-body-highlighter`'s copyrighted
14
+ SVG assets, its original MIT license and copyright notice are reproduced below, as required by
15
+ the MIT license's terms.
16
+
17
+ ---
18
+
19
+ ## react-native-body-highlighter — MIT License
20
+
21
+ MIT License
22
+
23
+ Copyright (c) 2022 ELABBASSI Hicham
24
+
25
+ Permission is hereby granted, free of charge, to any person obtaining a copy
26
+ of this software and associated documentation files (the "Software"), to deal
27
+ in the Software without restriction, including without limitation the rights
28
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
29
+ copies of the Software, and to permit persons to whom the Software is
30
+ furnished to do so, subject to the following conditions:
31
+
32
+ The above copyright notice and this permission notice shall be included in all
33
+ copies or substantial portions of the Software.
34
+
35
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
36
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
37
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
38
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
39
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
40
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
41
+ SOFTWARE.
@@ -0,0 +1,240 @@
1
+ "use strict";
2
+
3
+ import { useEffect } from 'react';
4
+ import { StyleSheet, View, Pressable, Text } from 'react-native';
5
+ import Svg, { Path } from 'react-native-svg';
6
+ import { GestureDetector, Gesture } from 'react-native-gesture-handler';
7
+ import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
8
+ import { BODY_REGIONS } from "./data/bodyRegions.generated.js";
9
+ import { useReducedMotion } from "./hooks/useReducedMotion.js";
10
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
+ function viewBoxAspectRatio(viewBox) {
12
+ const tokens = viewBox.split(' ');
13
+ const width = Number(tokens[2]);
14
+ const height = Number(tokens[3]);
15
+ return width / height;
16
+ }
17
+
18
+ // Only four gender/view combinations exist (BODY_REGIONS is static generated
19
+ // data), so their aspect ratios are precomputed once here rather than built
20
+ // as a fresh style object inline on every render.
21
+ const CONTAINER_ASPECT_RATIO_STYLE = {
22
+ 'male-front': {
23
+ aspectRatio: viewBoxAspectRatio(BODY_REGIONS.male.front.viewBox)
24
+ },
25
+ 'male-back': {
26
+ aspectRatio: viewBoxAspectRatio(BODY_REGIONS.male.back.viewBox)
27
+ },
28
+ 'female-front': {
29
+ aspectRatio: viewBoxAspectRatio(BODY_REGIONS.female.front.viewBox)
30
+ },
31
+ 'female-back': {
32
+ aspectRatio: viewBoxAspectRatio(BODY_REGIONS.female.back.viewBox)
33
+ }
34
+ };
35
+ function clampWorklet(value, min, max) {
36
+ 'worklet';
37
+
38
+ return Math.min(Math.max(value, min), max);
39
+ }
40
+ function maxTranslateWorklet(currentScale, containerSize) {
41
+ 'worklet';
42
+
43
+ return Math.max(0, containerSize * (currentScale - 1) / 2);
44
+ }
45
+ export function BodySilhouette({
46
+ gender,
47
+ view,
48
+ selectedSlugs,
49
+ colorForSlug,
50
+ onFragmentPress,
51
+ zoomable = true,
52
+ outlineColor = '#333333',
53
+ unselectedFragmentColor = '#D6D6D6',
54
+ selectedFragmentColor = '#2563EB',
55
+ zoomButtonBackgroundColor = '#FFFFFF',
56
+ zoomButtonBorderColor = '#D6D6D6',
57
+ zoomButtonIconColor = '#333333',
58
+ zoomInAccessibilityLabel = 'Zoom in',
59
+ zoomOutAccessibilityLabel = 'Zoom out',
60
+ minScale = 1,
61
+ maxScale = 3,
62
+ zoomStep = 0.5,
63
+ zoomAnimationDurationMs = 200
64
+ }) {
65
+ const reducedMotion = useReducedMotion();
66
+ const regionSet = BODY_REGIONS[gender][view];
67
+ const aspectRatioStyle = CONTAINER_ASPECT_RATIO_STYLE[`${gender}-${view}`];
68
+ const scale = useSharedValue(1);
69
+ const savedScale = useSharedValue(1);
70
+ const translateX = useSharedValue(0);
71
+ const translateY = useSharedValue(0);
72
+ const savedTranslateX = useSharedValue(0);
73
+ const savedTranslateY = useSharedValue(0);
74
+ const containerWidth = useSharedValue(0);
75
+ const containerHeight = useSharedValue(0);
76
+
77
+ // A zoom/pan carried over onto a different gender or view is confusing,
78
+ // not helpful — always start fresh when the displayed image changes.
79
+ useEffect(() => {
80
+ scale.value = 1;
81
+ savedScale.value = 1;
82
+ translateX.value = 0;
83
+ translateY.value = 0;
84
+ savedTranslateX.value = 0;
85
+ savedTranslateY.value = 0;
86
+ }, [gender, view, scale, savedScale, translateX, translateY, savedTranslateX, savedTranslateY]);
87
+ const pinchGesture = Gesture.Pinch().onUpdate(event => {
88
+ scale.value = clampWorklet(savedScale.value * event.scale, minScale, maxScale);
89
+ }).onEnd(() => {
90
+ savedScale.value = scale.value;
91
+ const maxX = maxTranslateWorklet(scale.value, containerWidth.value);
92
+ const maxY = maxTranslateWorklet(scale.value, containerHeight.value);
93
+ translateX.value = clampWorklet(translateX.value, -maxX, maxX);
94
+ translateY.value = clampWorklet(translateY.value, -maxY, maxY);
95
+ savedTranslateX.value = translateX.value;
96
+ savedTranslateY.value = translateY.value;
97
+ });
98
+ const panGesture = Gesture.Pan().onUpdate(event => {
99
+ if (savedScale.value <= minScale) return;
100
+ const maxX = maxTranslateWorklet(savedScale.value, containerWidth.value);
101
+ const maxY = maxTranslateWorklet(savedScale.value, containerHeight.value);
102
+ translateX.value = clampWorklet(savedTranslateX.value + event.translationX, -maxX, maxX);
103
+ translateY.value = clampWorklet(savedTranslateY.value + event.translationY, -maxY, maxY);
104
+ }).onEnd(() => {
105
+ savedTranslateX.value = translateX.value;
106
+ savedTranslateY.value = translateY.value;
107
+ });
108
+ const composedGesture = Gesture.Simultaneous(pinchGesture, panGesture);
109
+ const animatedStyle = useAnimatedStyle(() => ({
110
+ transform: [{
111
+ translateX: translateX.value
112
+ }, {
113
+ translateY: translateY.value
114
+ }, {
115
+ scale: scale.value
116
+ }]
117
+ }));
118
+ const applyZoomStep = direction => {
119
+ const nextScale = clampWorklet(savedScale.value + direction * zoomStep, minScale, maxScale);
120
+ savedScale.value = nextScale;
121
+ scale.value = reducedMotion ? nextScale : withTiming(nextScale, {
122
+ duration: zoomAnimationDurationMs
123
+ });
124
+ if (nextScale <= minScale) {
125
+ savedTranslateX.value = 0;
126
+ savedTranslateY.value = 0;
127
+ translateX.value = reducedMotion ? 0 : withTiming(0, {
128
+ duration: zoomAnimationDurationMs
129
+ });
130
+ translateY.value = reducedMotion ? 0 : withTiming(0, {
131
+ duration: zoomAnimationDurationMs
132
+ });
133
+ }
134
+ };
135
+ const handleLayout = event => {
136
+ containerWidth.value = event.nativeEvent.layout.width;
137
+ containerHeight.value = event.nativeEvent.layout.height;
138
+ };
139
+ const diagramContent = /*#__PURE__*/_jsxs(Svg, {
140
+ viewBox: regionSet.viewBox,
141
+ width: "100%",
142
+ height: "100%",
143
+ accessible: true,
144
+ accessibilityLabel: `${gender}-body-${view}`,
145
+ children: [/*#__PURE__*/_jsx(Path, {
146
+ d: regionSet.outlineD,
147
+ stroke: outlineColor,
148
+ strokeWidth: 2,
149
+ fill: "none",
150
+ vectorEffect: "non-scaling-stroke"
151
+ }), regionSet.fragments.map(fragment => {
152
+ const isSelected = selectedSlugs.includes(fragment.slug);
153
+ const fillColor = isSelected ? colorForSlug?.(fragment.slug) ?? selectedFragmentColor : unselectedFragmentColor;
154
+ return /*#__PURE__*/_jsx(Path, {
155
+ d: fragment.pathData,
156
+ fill: fillColor,
157
+ onPressIn: onFragmentPress ? () => onFragmentPress(fragment.slug) : undefined
158
+ }, fragment.slug);
159
+ })]
160
+ });
161
+ if (!zoomable) {
162
+ return /*#__PURE__*/_jsx(View, {
163
+ style: [localStyles.container, aspectRatioStyle],
164
+ children: diagramContent
165
+ });
166
+ }
167
+ const zoomButtonStyle = {
168
+ backgroundColor: zoomButtonBackgroundColor,
169
+ borderColor: zoomButtonBorderColor
170
+ };
171
+ return /*#__PURE__*/_jsxs(View, {
172
+ style: [localStyles.zoomableContainer, aspectRatioStyle],
173
+ onLayout: handleLayout,
174
+ children: [/*#__PURE__*/_jsx(GestureDetector, {
175
+ gesture: composedGesture,
176
+ children: /*#__PURE__*/_jsx(Animated.View, {
177
+ style: [localStyles.container, animatedStyle],
178
+ children: diagramContent
179
+ })
180
+ }), /*#__PURE__*/_jsxs(View, {
181
+ style: localStyles.zoomControls,
182
+ children: [/*#__PURE__*/_jsx(Pressable, {
183
+ style: [localStyles.zoomButton, zoomButtonStyle],
184
+ onPress: () => applyZoomStep(-1),
185
+ hitSlop: 8,
186
+ accessibilityRole: "button",
187
+ accessibilityLabel: zoomOutAccessibilityLabel,
188
+ children: /*#__PURE__*/_jsx(Text, {
189
+ style: [localStyles.zoomButtonGlyph, {
190
+ color: zoomButtonIconColor
191
+ }],
192
+ children: "\u2212"
193
+ })
194
+ }), /*#__PURE__*/_jsx(Pressable, {
195
+ style: [localStyles.zoomButton, zoomButtonStyle],
196
+ onPress: () => applyZoomStep(1),
197
+ hitSlop: 8,
198
+ accessibilityRole: "button",
199
+ accessibilityLabel: zoomInAccessibilityLabel,
200
+ children: /*#__PURE__*/_jsx(Text, {
201
+ style: [localStyles.zoomButtonGlyph, {
202
+ color: zoomButtonIconColor
203
+ }],
204
+ children: "+"
205
+ })
206
+ })]
207
+ })]
208
+ });
209
+ }
210
+ const localStyles = StyleSheet.create({
211
+ container: {
212
+ width: '100%',
213
+ height: '100%'
214
+ },
215
+ zoomableContainer: {
216
+ width: '100%',
217
+ overflow: 'hidden'
218
+ },
219
+ zoomControls: {
220
+ position: 'absolute',
221
+ bottom: 8,
222
+ end: 8,
223
+ flexDirection: 'row',
224
+ gap: 8
225
+ },
226
+ zoomButton: {
227
+ width: 36,
228
+ height: 36,
229
+ borderRadius: 18,
230
+ borderWidth: 1,
231
+ alignItems: 'center',
232
+ justifyContent: 'center'
233
+ },
234
+ zoomButtonGlyph: {
235
+ fontSize: 16,
236
+ fontWeight: '600',
237
+ lineHeight: 18
238
+ }
239
+ });
240
+ //# sourceMappingURL=BodySilhouette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["useEffect","StyleSheet","View","Pressable","Text","Svg","Path","GestureDetector","Gesture","Animated","useSharedValue","useAnimatedStyle","withTiming","BODY_REGIONS","useReducedMotion","jsx","_jsx","jsxs","_jsxs","viewBoxAspectRatio","viewBox","tokens","split","width","Number","height","CONTAINER_ASPECT_RATIO_STYLE","aspectRatio","male","front","back","female","clampWorklet","value","min","max","Math","maxTranslateWorklet","currentScale","containerSize","BodySilhouette","gender","view","selectedSlugs","colorForSlug","onFragmentPress","zoomable","outlineColor","unselectedFragmentColor","selectedFragmentColor","zoomButtonBackgroundColor","zoomButtonBorderColor","zoomButtonIconColor","zoomInAccessibilityLabel","zoomOutAccessibilityLabel","minScale","maxScale","zoomStep","zoomAnimationDurationMs","reducedMotion","regionSet","aspectRatioStyle","scale","savedScale","translateX","translateY","savedTranslateX","savedTranslateY","containerWidth","containerHeight","pinchGesture","Pinch","onUpdate","event","onEnd","maxX","maxY","panGesture","Pan","translationX","translationY","composedGesture","Simultaneous","animatedStyle","transform","applyZoomStep","direction","nextScale","duration","handleLayout","nativeEvent","layout","diagramContent","accessible","accessibilityLabel","children","d","outlineD","stroke","strokeWidth","fill","vectorEffect","fragments","map","fragment","isSelected","includes","slug","fillColor","pathData","onPressIn","undefined","style","localStyles","container","zoomButtonStyle","backgroundColor","borderColor","zoomableContainer","onLayout","gesture","zoomControls","zoomButton","onPress","hitSlop","accessibilityRole","zoomButtonGlyph","color","create","overflow","position","bottom","end","flexDirection","gap","borderRadius","borderWidth","alignItems","justifyContent","fontSize","fontWeight","lineHeight"],"sourceRoot":"../../src","sources":["BodySilhouette.tsx"],"mappings":"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","ignoreList":[]}