react-native-earl-gamepad 0.3.0 → 0.4.1

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 CHANGED
@@ -16,9 +16,9 @@ WebView-based gamepad bridge for React Native. Polls `navigator.getGamepads()` i
16
16
  ## Install
17
17
 
18
18
  ```sh
19
- npm install react-native-earl-gamepad react-native-webview
19
+ npm install react-native-earl-gamepad
20
20
  # or
21
- yarn add react-native-earl-gamepad react-native-webview
21
+ yarn add react-native-earl-gamepad
22
22
  ```
23
23
 
24
24
  ## Usage
@@ -85,6 +85,10 @@ export function DebugScreen() {
85
85
  }
86
86
  ```
87
87
 
88
+ ![Gamepad visual idle](https://github.com/user-attachments/assets/dfebd8c5-7d9a-42c7-802b-2773ec8c8ae9)
89
+
90
+ ![Gamepad visual pressed](https://github.com/user-attachments/assets/7b37d76a-7695-4be9-bda4-7e3d1e6adf41)
91
+
88
92
  ## API
89
93
 
90
94
  ### `GamepadBridge` props
@@ -109,6 +113,7 @@ Return shape:
109
113
 
110
114
  - `pressedButtons: Set<GamepadButtonName>` — current pressed buttons.
111
115
  - `axes: Partial<Record<StickAxisName, number>>` — axis values with deadzone applied.
116
+ - `buttonValues: Partial<Record<GamepadButtonName, number>>` — last analog value per button (useful for LT/RT triggers).
112
117
  - `isPressed(key: GamepadButtonName): boolean` — helper to check a single button.
113
118
  - `bridge: JSX.Element | null` — render once to enable polling.
114
119
 
@@ -133,6 +138,7 @@ Button names map to the standard gamepad layout (`a`, `b`, `x`, `y`, `lb`, `rb`,
133
138
  - On disconnect, pressed state is cleared and release events are emitted so you do not get stuck buttons.
134
139
  - Keep the bridge mounted; remounting clears internal state and can drop transient events.
135
140
  - Axis values below the deadzone are coerced to `0`. Adjust `axisThreshold` if you need more sensitivity.
141
+ - LT/RT expose analog values via `buttonValues.lt` and `buttonValues.rt`.
136
142
 
137
143
  ## Patterns
138
144
 
@@ -147,6 +153,11 @@ npm install
147
153
  npm run build
148
154
  ```
149
155
 
156
+ ## Troubleshooting
157
+
158
+ - **[Invariant Violation: Tried to register two views with the same name RNCWebView]**: Check your `package.json` for multiple instances of `react-native-webview` and uninstall any duplicates.
159
+ When you install `react-native-earl-gamepad`, `react-native-webview` is already included, so you should not install it separately. or you can check it by running `npm ls react-native-webview`.
160
+
150
161
  Build outputs to `dist/` with type declarations.
151
162
 
152
163
  ## License
@@ -10,111 +10,165 @@ const react_native_1 = require("react-native");
10
10
  const useGamepad_1 = __importDefault(require("./useGamepad"));
11
11
  const trackedAxes = ["leftX", "leftY", "rightX", "rightY"];
12
12
  const format = (value) => (value !== null && value !== void 0 ? value : 0).toFixed(2);
13
+ function ControllerVisual({ pressed, axis, value }) {
14
+ const level = (name) => {
15
+ const v = value(name);
16
+ return Math.max(v, pressed(name) ? 1 : 0);
17
+ };
18
+ const mix = (a, b, t) => Math.round(a + (b - a) * t);
19
+ const stickColor = (mag) => {
20
+ const clamped = Math.min(1, Math.max(0, mag));
21
+ const r = mix(34, 37, clamped);
22
+ const g = mix(34, 99, clamped);
23
+ const b = mix(34, 235, clamped);
24
+ return `rgb(${r}, ${g}, ${b})`;
25
+ };
26
+ const stickInner = (mag) => {
27
+ const clamped = Math.min(1, Math.max(0, mag));
28
+ const shade = mix(109, 200, clamped);
29
+ return `rgb(${shade}, ${shade}, ${shade})`;
30
+ };
31
+ const leftMag = Math.min(1, Math.hypot(axis("leftX"), axis("leftY")));
32
+ const rightMag = Math.min(1, Math.hypot(axis("rightX"), axis("rightY")));
33
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psWrapper, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psContainer, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psShoulderRow, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
34
+ styles.psShoulder,
35
+ styles.psShoulderLeft,
36
+ level("lb") > 0 && styles.psShoulderActive,
37
+ { opacity: 0.55 + 0.45 * level("lb") },
38
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
39
+ styles.psShoulderText,
40
+ level("lb") > 0 && styles.psShoulderTextActive,
41
+ ], children: "L1" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
42
+ styles.psShoulder,
43
+ styles.psShoulderLeft,
44
+ level("lt") > 0 && styles.psShoulderActive,
45
+ { opacity: 0.55 + 0.45 * level("lt") },
46
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
47
+ styles.psShoulderText,
48
+ level("lt") > 0 && styles.psShoulderTextActive,
49
+ ], children: "L2" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
50
+ styles.psShoulder,
51
+ styles.psShoulderRight,
52
+ level("rb") > 0 && styles.psShoulderActive,
53
+ { opacity: 0.55 + 0.45 * level("rb") },
54
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
55
+ styles.psShoulderText,
56
+ level("rb") > 0 && styles.psShoulderTextActive,
57
+ ], children: "R1" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
58
+ styles.psShoulder,
59
+ styles.psShoulderRight,
60
+ level("rt") > 0 && styles.psShoulderActive,
61
+ { opacity: 0.55 + 0.45 * level("rt") },
62
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: [
63
+ styles.psShoulderText,
64
+ level("rt") > 0 && styles.psShoulderTextActive,
65
+ ], children: "R2" }) })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psMiddle }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psPaveTactile }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
66
+ styles.psShare,
67
+ pressed("back") && styles.psActiveButton,
68
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
69
+ styles.psOptions,
70
+ pressed("start") && styles.psActiveButton,
71
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.psLeftHand, children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psLeftPad, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
72
+ styles.psArrowUp,
73
+ pressed("dpadUp") && styles.psActiveButton,
74
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
75
+ styles.psArrowDown,
76
+ pressed("dpadDown") && styles.psActiveButton,
77
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
78
+ styles.psArrowRight,
79
+ pressed("dpadRight") && styles.psActiveButton,
80
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
81
+ styles.psArrowLeft,
82
+ pressed("dpadLeft") && styles.psActiveButton,
83
+ ] })] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psRightHand, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.psRightPad, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
84
+ styles.psTriangle,
85
+ pressed("y") && styles.psActiveButton,
86
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
87
+ styles.psTriangleBas,
88
+ pressed("y") && styles.psTriangleLineActive,
89
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
90
+ styles.psTriangleGauche,
91
+ pressed("y") && styles.psTriangleLineActive,
92
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
93
+ styles.psTriangleDroit,
94
+ pressed("y") && styles.psTriangleLineActive,
95
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
96
+ styles.psCarre,
97
+ pressed("x") && styles.psActiveButton,
98
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
99
+ styles.psCarreRose,
100
+ pressed("x") && styles.psPinkActive,
101
+ ] }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
102
+ styles.psRond,
103
+ pressed("b") && styles.psActiveButton,
104
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
105
+ styles.psRondRouge,
106
+ pressed("b") && styles.psRedActive,
107
+ ] }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [
108
+ styles.psCroix,
109
+ pressed("a") && styles.psActiveButton,
110
+ ], children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
111
+ styles.psCroixBleue,
112
+ pressed("a") && styles.psCrossLineActive,
113
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
114
+ styles.psCroixBleue2,
115
+ pressed("a") && styles.psCrossLineActive,
116
+ ] })] })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
117
+ styles.psRollLeft,
118
+ pressed("ls") && styles.psStickPressed,
119
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
120
+ styles.psRollIn,
121
+ {
122
+ transform: [
123
+ { translateX: axis("leftX") * 6 },
124
+ { translateY: axis("leftY") * -6 },
125
+ ],
126
+ backgroundColor: stickColor(leftMag),
127
+ },
128
+ pressed("ls") && styles.psStickInnerPressed,
129
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
130
+ styles.psRollInIn,
131
+ { backgroundColor: stickInner(leftMag) },
132
+ pressed("ls") &&
133
+ styles.psStickCenterPressed,
134
+ ] }) }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
135
+ styles.psPsButton,
136
+ pressed("home") && styles.psActiveButton,
137
+ ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
138
+ styles.psRollRight,
139
+ pressed("rs") && styles.psStickPressed,
140
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
141
+ styles.psRollIn,
142
+ {
143
+ transform: [
144
+ { translateX: axis("rightX") * 6 },
145
+ { translateY: axis("rightY") * -6 },
146
+ ],
147
+ backgroundColor: stickColor(rightMag),
148
+ },
149
+ pressed("rs") && styles.psStickInnerPressed,
150
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
151
+ styles.psRollInIn,
152
+ { backgroundColor: stickInner(rightMag) },
153
+ pressed("rs") &&
154
+ styles.psStickCenterPressed,
155
+ ] }) }) })] })] }) }));
156
+ }
13
157
  function GamepadDebug({ enabled = true, axisThreshold = 0.15, }) {
14
- const { bridge, pressedButtons, axes } = (0, useGamepad_1.default)({
158
+ const { bridge, pressedButtons, axes, buttonValues } = (0, useGamepad_1.default)({
15
159
  enabled,
16
160
  axisThreshold,
17
161
  });
18
162
  const pressedList = (0, react_1.useMemo)(() => Array.from(pressedButtons).sort(), [pressedButtons]);
19
163
  const pressed = (key) => pressedButtons.has(key);
20
- const axis = (key) => { var _a; return (_a = axes[key]) !== null && _a !== void 0 ? _a : 0; };
21
- return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.container, children: (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { contentContainerStyle: styles.scrollContent, showsVerticalScrollIndicator: false, children: [bridge, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.headerRow, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.title, children: "Gamepad Debug" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.tag, children: (0, jsx_runtime_1.jsxs)(react_native_1.Text, { style: styles.tagText, children: ["Enabled ", enabled ? "On" : "Off"] }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.body, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.controllerCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "Controller" }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.controller, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.shoulders, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
22
- styles.bumper,
23
- pressed("lb") && styles.active,
24
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "LB" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
25
- styles.bumper,
26
- pressed("rb") && styles.active,
27
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "RB" }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.triggers, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
28
- styles.trigger,
29
- pressed("lt") && styles.active,
30
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "LT" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
31
- styles.trigger,
32
- pressed("rt") && styles.active,
33
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "RT" }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.midRow, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.stickZone, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.stickRing, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
34
- styles.stick,
35
- {
36
- transform: [
37
- {
38
- translateX: axis("leftX") *
39
- 20,
40
- },
41
- {
42
- translateY: axis("leftY") *
43
- -20,
44
- },
45
- ],
46
- },
47
- pressed("ls") && styles.active,
48
- ] }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.smallLabel, children: "LS" })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.centerCluster, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
49
- styles.smallKey,
50
- pressed("back") && styles.active,
51
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "Back" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
52
- styles.smallKey,
53
- pressed("start") && styles.active,
54
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "Start" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
55
- styles.smallKey,
56
- pressed("home") && styles.active,
57
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.label, children: "Home" }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.faceCluster, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.faceRow, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
58
- styles.faceButton,
59
- pressed("y") &&
60
- styles.faceActive,
61
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "Y" }) }) }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.faceRow, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
62
- styles.faceButton,
63
- pressed("x") &&
64
- styles.faceActive,
65
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "X" }) }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
66
- styles.faceButton,
67
- pressed("b") &&
68
- styles.faceActive,
69
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "B" }) })] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.faceRow, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
70
- styles.faceButton,
71
- pressed("a") &&
72
- styles.faceActive,
73
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.faceText, children: "A" }) }) })] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.bottomRow, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.dpad, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
74
- styles.dpadKey,
75
- styles.dpadVertical,
76
- styles.dpadUp,
77
- pressed("dpadUp") && styles.active,
78
- ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
79
- styles.dpadKey,
80
- styles.dpadVertical,
81
- styles.dpadDown,
82
- pressed("dpadDown") &&
83
- styles.active,
84
- ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
85
- styles.dpadKey,
86
- styles.dpadHorizontal,
87
- styles.dpadLeft,
88
- pressed("dpadLeft") &&
89
- styles.active,
90
- ] }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
91
- styles.dpadKey,
92
- styles.dpadHorizontal,
93
- styles.dpadRight,
94
- pressed("dpadRight") &&
95
- styles.active,
96
- ] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.stickZone, children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.stickRing, children: (0, jsx_runtime_1.jsx)(react_native_1.View, { style: [
97
- styles.stick,
98
- {
99
- transform: [
100
- {
101
- translateX: axis("rightX") *
102
- 20,
103
- },
104
- {
105
- translateY: axis("rightY") *
106
- -20,
107
- },
108
- ],
109
- },
110
- pressed("rs") && styles.active,
111
- ] }) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.smallLabel, children: "RS" })] })] })] })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.stateCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "State" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Pressed" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badgeRow, children: pressedList.length === 0 ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.muted, children: "None" })) : (pressedList.map((name) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badge, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.badgeText, children: name }) }, name)))) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Axes" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.axesGrid, children: trackedAxes.map((axisName) => ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.axisItem, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisLabel, children: axisName }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisValue, children: format(axes[axisName]) })] }, axisName))) })] })] })] }) }));
164
+ const axisValue = (key) => { var _a; return (_a = axes[key]) !== null && _a !== void 0 ? _a : 0; };
165
+ const buttonValue = (key) => { var _a; return (_a = buttonValues[key]) !== null && _a !== void 0 ? _a : 0; };
166
+ return ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.container, children: (0, jsx_runtime_1.jsxs)(react_native_1.ScrollView, { contentContainerStyle: styles.scrollContent, showsVerticalScrollIndicator: false, children: [bridge, (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.body, children: [(0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.controllerCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "Controller" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.controller, children: (0, jsx_runtime_1.jsx)(ControllerVisual, { pressed: pressed, axis: axisValue, value: buttonValue }) })] }), (0, jsx_runtime_1.jsxs)(react_native_1.View, { style: [styles.card, styles.stateCard], children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.cardTitle, children: "State" }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Pressed" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badgeRow, children: pressedList.length === 0 ? ((0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.muted, children: "None" })) : (pressedList.map((name) => ((0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.badge, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.badgeText, children: name }) }, name)))) }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.sectionTitle, children: "Axes" }), (0, jsx_runtime_1.jsx)(react_native_1.View, { style: styles.axesGrid, children: trackedAxes.map((axisName) => ((0, jsx_runtime_1.jsxs)(react_native_1.View, { style: styles.axisItem, children: [(0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisLabel, children: axisName }), (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.axisValue, children: format(axes[axisName]) })] }, axisName))) })] })] })] }) }));
112
167
  }
113
168
  const styles = react_native_1.StyleSheet.create({
114
169
  container: {
115
170
  flex: 1,
116
171
  width: "100%",
117
- padding: 12,
118
172
  backgroundColor: "#f5f7fb",
119
173
  },
120
174
  scrollContent: {
@@ -209,7 +263,6 @@ const styles = react_native_1.StyleSheet.create({
209
263
  flexShrink: 1,
210
264
  flexBasis: "100%",
211
265
  backgroundColor: "#ffffff",
212
- borderRadius: 14,
213
266
  borderWidth: 1,
214
267
  borderColor: "#e2e8f0",
215
268
  padding: 12,
@@ -228,163 +281,471 @@ const styles = react_native_1.StyleSheet.create({
228
281
  },
229
282
  controller: {
230
283
  backgroundColor: "#f8fafc",
231
- borderRadius: 14,
232
284
  borderWidth: 1,
233
285
  borderColor: "#e2e8f0",
234
286
  padding: 12,
235
- gap: 12,
287
+ alignItems: "center",
288
+ justifyContent: "center",
236
289
  },
237
- shoulders: {
290
+ axesGrid: {
238
291
  flexDirection: "row",
239
- justifyContent: "space-between",
292
+ flexWrap: "wrap",
293
+ gap: 8,
294
+ },
295
+ psWrapper: {
296
+ alignItems: "center",
297
+ justifyContent: "center",
240
298
  },
241
- triggers: {
299
+ psShoulderRow: {
300
+ position: "absolute",
301
+ top: 10,
302
+ left: 0,
303
+ right: 0,
242
304
  flexDirection: "row",
243
305
  justifyContent: "space-between",
244
- gap: 8,
306
+ paddingHorizontal: 24,
307
+ zIndex: 25,
245
308
  },
246
- bumper: {
247
- flex: 1,
309
+ psShoulder: {
310
+ minWidth: 62,
248
311
  paddingVertical: 8,
249
- borderRadius: 10,
312
+ paddingHorizontal: 14,
313
+ borderRadius: 999,
250
314
  backgroundColor: "#e2e8f0",
251
315
  borderWidth: 1,
252
316
  borderColor: "#cbd5e1",
253
317
  alignItems: "center",
318
+ shadowColor: "#000",
319
+ shadowOffset: { width: 0, height: 2 },
320
+ shadowOpacity: 0.25,
321
+ shadowRadius: 6,
322
+ elevation: 4,
254
323
  },
255
- trigger: {
256
- flex: 1,
257
- paddingVertical: 8,
258
- borderRadius: 10,
259
- backgroundColor: "#e2e8f0",
260
- borderWidth: 1,
261
- borderColor: "#cbd5e1",
262
- alignItems: "center",
324
+ psShoulderLeft: {
325
+ marginRight: 6,
263
326
  },
264
- label: {
327
+ psShoulderRight: {
328
+ marginLeft: 6,
329
+ },
330
+ psShoulderActive: {
331
+ backgroundColor: "#2563eb",
332
+ borderColor: "#1e3a8a",
333
+ shadowColor: "#2563eb",
334
+ shadowOpacity: 0.5,
335
+ },
336
+ psShoulderText: {
265
337
  color: "#0f172a",
266
- fontSize: 12,
267
338
  fontWeight: "700",
268
339
  },
269
- smallLabel: {
270
- color: "#475569",
271
- fontSize: 11,
272
- marginTop: 4,
273
- textAlign: "center",
340
+ psShoulderTextActive: {
341
+ color: "#e0f2fe",
274
342
  },
275
- midRow: {
276
- flexDirection: "row",
277
- alignItems: "center",
278
- justifyContent: "space-between",
279
- gap: 12,
343
+ psContainer: {
344
+ position: "relative",
345
+ width: 500,
346
+ height: 350,
347
+ },
348
+ psMiddle: {
349
+ width: 395,
350
+ height: 160,
351
+ backgroundColor: "#e0e0e0",
352
+ marginLeft: 27,
353
+ borderRadius: 25,
354
+ position: "absolute",
355
+ zIndex: 0,
356
+ top: 80,
357
+ },
358
+ psPaveTactile: {
359
+ width: 150,
360
+ height: 80,
361
+ backgroundColor: "#333",
362
+ marginLeft: 147,
363
+ borderRadius: 7,
364
+ position: "absolute",
365
+ zIndex: 10,
366
+ top: 80,
367
+ shadowColor: "#000",
368
+ shadowOffset: { width: 0, height: 0 },
369
+ shadowOpacity: 0.5,
370
+ shadowRadius: 10,
371
+ elevation: 5,
280
372
  },
281
- centerCluster: {
373
+ psShare: {
374
+ width: 12,
375
+ height: 25,
376
+ position: "absolute",
377
+ backgroundColor: "#95a5a6",
378
+ marginLeft: 125,
379
+ marginTop: 85,
380
+ borderRadius: 5,
381
+ zIndex: 10,
382
+ shadowColor: "#000",
383
+ shadowOffset: { width: 0, height: 2 },
384
+ shadowOpacity: 0.3,
385
+ shadowRadius: 5,
386
+ elevation: 3,
387
+ },
388
+ psOptions: {
389
+ width: 12,
390
+ height: 25,
391
+ position: "absolute",
392
+ backgroundColor: "#95a5a6",
393
+ marginLeft: 305,
394
+ marginTop: 85,
395
+ borderRadius: 5,
396
+ zIndex: 10,
397
+ shadowColor: "#000",
398
+ shadowOffset: { width: 0, height: 2 },
399
+ shadowOpacity: 0.3,
400
+ shadowRadius: 5,
401
+ elevation: 3,
402
+ },
403
+ psLeftHand: {
404
+ width: 120,
405
+ height: 260,
406
+ backgroundColor: "#e0e0e0",
407
+ position: "absolute",
408
+ left: 0,
409
+ top: 95,
410
+ transform: [{ rotate: "11deg" }],
411
+ borderTopLeftRadius: 30,
412
+ borderTopRightRadius: 50,
413
+ borderBottomRightRadius: 50,
414
+ borderBottomLeftRadius: 50,
415
+ shadowColor: "#000",
416
+ shadowOffset: { width: -5, height: 5 },
417
+ shadowOpacity: 0.4,
418
+ shadowRadius: 15,
419
+ elevation: 10,
420
+ zIndex: 1,
421
+ paddingTop: 5,
422
+ paddingLeft: 5,
423
+ },
424
+ psRightHand: {
425
+ width: 120,
426
+ height: 260,
427
+ backgroundColor: "#e0e0e0",
428
+ position: "absolute",
429
+ left: 330,
430
+ top: 95,
431
+ transform: [{ rotate: "-11deg" }],
432
+ borderTopLeftRadius: 30,
433
+ borderTopRightRadius: 30,
434
+ borderBottomRightRadius: 50,
435
+ borderBottomLeftRadius: 50,
436
+ shadowColor: "#000",
437
+ shadowOffset: { width: 5, height: 5 },
438
+ shadowOpacity: 0.4,
439
+ shadowRadius: 15,
440
+ elevation: 10,
441
+ zIndex: 1,
442
+ paddingTop: 2,
282
443
  alignItems: "center",
283
- gap: 6,
284
444
  },
285
- smallKey: {
286
- paddingVertical: 6,
287
- paddingHorizontal: 10,
288
- borderRadius: 8,
289
- backgroundColor: "#e2e8f0",
445
+ psLeftPad: {
446
+ backgroundColor: "#c0c0c0",
447
+ width: 112,
448
+ height: 112,
449
+ borderRadius: 56,
450
+ marginTop: 5,
451
+ marginLeft: 10,
290
452
  borderWidth: 1,
291
- borderColor: "#cbd5e1",
453
+ borderColor: "#b0b0b0",
454
+ position: "relative",
292
455
  },
293
- faceCluster: {
294
- alignItems: "center",
295
- gap: 4,
456
+ psArrowUp: {
457
+ width: 22,
458
+ height: 24,
459
+ backgroundColor: "#333",
460
+ position: "absolute",
461
+ top: 18,
462
+ left: 40,
463
+ transform: [{ rotate: "-11deg" }],
464
+ shadowColor: "#000",
465
+ shadowOffset: { width: 0, height: 4 },
466
+ shadowOpacity: 0.5,
467
+ shadowRadius: 5,
468
+ elevation: 5,
296
469
  },
297
- faceRow: {
298
- flexDirection: "row",
299
- gap: 8,
300
- justifyContent: "center",
470
+ psArrowDown: {
471
+ width: 22,
472
+ height: 24,
473
+ backgroundColor: "#333",
474
+ position: "absolute",
475
+ top: 70,
476
+ left: 50,
477
+ transform: [{ rotate: "-11deg" }],
478
+ shadowColor: "#000",
479
+ shadowOffset: { width: 0, height: 4 },
480
+ shadowOpacity: 0.5,
481
+ shadowRadius: 5,
482
+ elevation: 5,
301
483
  },
302
- faceButton: {
303
- width: 42,
304
- height: 42,
305
- borderRadius: 21,
306
- backgroundColor: "#e0f2fe",
484
+ psArrowRight: {
485
+ width: 24,
486
+ height: 22,
487
+ backgroundColor: "#333",
488
+ position: "absolute",
489
+ top: 40,
490
+ left: 72,
491
+ transform: [{ rotate: "-11deg" }],
492
+ shadowColor: "#000",
493
+ shadowOffset: { width: 0, height: 4 },
494
+ shadowOpacity: 0.5,
495
+ shadowRadius: 5,
496
+ elevation: 5,
497
+ },
498
+ psArrowLeft: {
499
+ width: 24,
500
+ height: 22,
501
+ backgroundColor: "#333",
502
+ position: "absolute",
503
+ top: 50,
504
+ left: 17,
505
+ transform: [{ rotate: "-11deg" }],
506
+ shadowColor: "#000",
507
+ shadowOffset: { width: 0, height: 4 },
508
+ shadowOpacity: 0.5,
509
+ shadowRadius: 5,
510
+ elevation: 5,
511
+ },
512
+ psRightPad: {
513
+ backgroundColor: "#c0c0c0",
514
+ width: 112,
515
+ height: 112,
516
+ borderRadius: 56,
517
+ marginTop: 2,
307
518
  borderWidth: 1,
308
- borderColor: "#bae6fd",
519
+ borderColor: "#b0b0b0",
520
+ position: "relative",
521
+ },
522
+ psTriangle: {
523
+ width: 30,
524
+ height: 30,
525
+ position: "absolute",
526
+ borderRadius: 15,
527
+ marginTop: 7,
528
+ marginLeft: 49,
529
+ backgroundColor: "#e0e0e0",
530
+ shadowColor: "#000",
531
+ shadowOffset: { width: 2, height: 2 },
532
+ shadowOpacity: 0.3,
533
+ shadowRadius: 5,
534
+ elevation: 3,
535
+ },
536
+ psTriangleBas: {
537
+ width: 22,
538
+ height: 22,
539
+ position: "absolute",
540
+ marginTop: 27,
541
+ marginLeft: 50,
542
+ transform: [{ rotate: "11deg" }],
543
+ borderTopWidth: 3,
544
+ borderTopColor: "#00c081",
545
+ },
546
+ psTriangleDroit: {
547
+ width: 20,
548
+ height: 20,
549
+ position: "absolute",
550
+ marginTop: 14,
551
+ marginLeft: 51,
552
+ transform: [{ rotate: "67deg" }],
553
+ borderTopWidth: 3,
554
+ borderTopColor: "#00c081",
555
+ },
556
+ psTriangleGauche: {
557
+ width: 20,
558
+ height: 20,
559
+ position: "absolute",
560
+ marginTop: 15,
561
+ marginLeft: 56,
562
+ transform: [{ rotate: "-46deg" }],
563
+ borderTopWidth: 3,
564
+ borderTopColor: "#00c081",
565
+ },
566
+ psCroix: {
567
+ width: 30,
568
+ height: 30,
569
+ position: "absolute",
570
+ borderRadius: 15,
571
+ marginTop: 70,
572
+ marginLeft: 36,
573
+ backgroundColor: "#e0e0e0",
574
+ shadowColor: "#000",
575
+ shadowOffset: { width: 2, height: 2 },
576
+ shadowOpacity: 0.3,
577
+ shadowRadius: 5,
578
+ elevation: 3,
579
+ },
580
+ psCroixBleue: {
581
+ width: 24,
582
+ height: 24,
583
+ transform: [{ rotate: "53deg" }],
584
+ borderRightWidth: 3,
585
+ borderRightColor: "#0a86e5",
586
+ marginTop: -7,
587
+ marginLeft: -4,
588
+ },
589
+ psCroixBleue2: {
590
+ width: 24,
591
+ height: 24,
592
+ transform: [{ rotate: "-32deg" }],
593
+ borderRightWidth: 3,
594
+ borderRightColor: "#0a86e5",
595
+ marginTop: -9,
596
+ marginLeft: -6,
597
+ },
598
+ psCarre: {
599
+ width: 30,
600
+ height: 30,
601
+ position: "absolute",
602
+ borderRadius: 15,
603
+ marginTop: 32,
604
+ marginLeft: 10,
605
+ backgroundColor: "#e0e0e0",
606
+ shadowColor: "#000",
607
+ shadowOffset: { width: 2, height: 2 },
608
+ shadowOpacity: 0.3,
609
+ shadowRadius: 5,
610
+ elevation: 3,
611
+ },
612
+ psCarreRose: {
613
+ width: 17,
614
+ height: 17,
615
+ marginTop: 6,
616
+ marginLeft: 6,
617
+ transform: [{ rotate: "11deg" }],
618
+ borderWidth: 3,
619
+ borderColor: "#e95ce9",
620
+ },
621
+ psRond: {
622
+ width: 30,
623
+ height: 30,
624
+ position: "absolute",
625
+ borderRadius: 15,
626
+ marginTop: 44,
627
+ marginLeft: 74,
628
+ backgroundColor: "#e0e0e0",
629
+ shadowColor: "#000",
630
+ shadowOffset: { width: 2, height: 2 },
631
+ shadowOpacity: 0.3,
632
+ shadowRadius: 5,
633
+ elevation: 3,
634
+ },
635
+ psRondRouge: {
636
+ width: 16,
637
+ height: 16,
638
+ borderRadius: 8,
639
+ transform: [{ rotate: "11deg" }],
640
+ borderWidth: 3,
641
+ borderColor: "#ff3746",
642
+ marginTop: 7,
643
+ marginLeft: 7,
644
+ },
645
+ psRollLeft: {
646
+ width: 60,
647
+ height: 60,
648
+ backgroundColor: "#333",
649
+ position: "absolute",
650
+ borderRadius: 40,
651
+ left: -180,
652
+ top: 40,
653
+ zIndex: 5,
654
+ shadowColor: "#000",
655
+ shadowOffset: { width: 0, height: 5 },
656
+ shadowOpacity: 0.5,
657
+ shadowRadius: 10,
658
+ elevation: 6,
309
659
  alignItems: "center",
310
660
  justifyContent: "center",
311
661
  },
312
- faceText: {
313
- color: "#0f172a",
314
- fontWeight: "800",
315
- },
316
- faceActive: {
317
- backgroundColor: "#2563eb",
318
- borderColor: "#1d4ed8",
319
- shadowColor: "#2563eb",
320
- shadowOpacity: 0.6,
321
- shadowRadius: 6,
322
- },
323
- stickZone: {
324
- width: 96,
662
+ psRollRight: {
663
+ width: 60,
664
+ height: 60,
665
+ backgroundColor: "#333",
666
+ position: "absolute",
667
+ borderRadius: 40,
668
+ left: -73,
669
+ top: 62,
670
+ zIndex: 5,
671
+ shadowColor: "#000",
672
+ shadowOffset: { width: 0, height: 5 },
673
+ shadowOpacity: 0.5,
674
+ shadowRadius: 10,
675
+ elevation: 6,
325
676
  alignItems: "center",
326
- gap: 6,
677
+ justifyContent: "center",
327
678
  },
328
- stickRing: {
329
- width: 84,
330
- height: 84,
331
- borderRadius: 42,
679
+ psRollIn: {
680
+ width: 40,
681
+ height: 40,
682
+ backgroundColor: "#222",
683
+ position: "absolute",
684
+ borderRadius: 30,
685
+ left: 10,
686
+ top: 10,
332
687
  borderWidth: 1,
333
- borderColor: "#cbd5e1",
334
- backgroundColor: "#f8fafc",
688
+ borderColor: "#000",
335
689
  alignItems: "center",
336
690
  justifyContent: "center",
337
691
  },
338
- stick: {
339
- width: 38,
340
- height: 38,
341
- borderRadius: 19,
342
- backgroundColor: "#e2e8f0",
343
- borderWidth: 2,
344
- borderColor: "#cbd5e1",
692
+ psRollInIn: {
693
+ width: 22,
694
+ height: 22,
695
+ backgroundColor: "#6d6d6d",
696
+ position: "absolute",
697
+ borderRadius: 22.5,
698
+ left: 8,
699
+ top: 8,
700
+ },
701
+ psPsButton: {
702
+ width: 23,
703
+ height: 23,
704
+ backgroundColor: "#333",
705
+ position: "absolute",
706
+ borderRadius: 11.5,
707
+ left: -110,
708
+ top: 85,
709
+ zIndex: 10,
710
+ borderWidth: 1,
711
+ borderColor: "#555",
712
+ shadowColor: "#000",
713
+ shadowOffset: { width: 0, height: 0 },
714
+ shadowOpacity: 0.5,
715
+ shadowRadius: 7,
716
+ elevation: 4,
345
717
  },
346
- bottomRow: {
347
- flexDirection: "row",
348
- justifyContent: "space-between",
349
- alignItems: "center",
350
- gap: 12,
718
+ psActiveButton: {
719
+ backgroundColor: "#2563eb",
720
+ borderColor: "#1e3a8a",
721
+ shadowColor: "#2563eb",
722
+ shadowOpacity: 0.4,
723
+ shadowRadius: 6,
351
724
  },
352
- dpad: {
353
- width: 120,
354
- height: 120,
355
- alignItems: "center",
356
- justifyContent: "center",
357
- position: "relative",
725
+ psTriangleLineActive: {
726
+ borderTopColor: "#00f5a8",
358
727
  },
359
- dpadKey: {
360
- position: "absolute",
361
- backgroundColor: "#e2e8f0",
362
- borderColor: "#cbd5e1",
363
- borderWidth: 1,
364
- borderRadius: 6,
728
+ psPinkActive: {
729
+ borderColor: "#d946ef",
365
730
  },
366
- dpadVertical: {
367
- width: 28,
368
- height: 46,
731
+ psRedActive: {
732
+ borderColor: "#fb7185",
369
733
  },
370
- dpadHorizontal: {
371
- width: 46,
372
- height: 28,
734
+ psCrossLineActive: {
735
+ borderRightColor: "#2563eb",
373
736
  },
374
- active: {
375
- borderColor: "#2563eb",
376
- backgroundColor: "#2563eb",
737
+ psStickPressed: {
738
+ shadowOpacity: 0.65,
739
+ shadowRadius: 12,
740
+ },
741
+ psStickInnerPressed: {
742
+ borderColor: "#1e3a8a",
377
743
  shadowColor: "#2563eb",
378
- shadowOpacity: 0.5,
379
- shadowRadius: 4,
744
+ shadowOpacity: 0.45,
745
+ shadowRadius: 10,
380
746
  },
381
- dpadUp: { top: 6, left: 46 },
382
- dpadDown: { bottom: 6, left: 46 },
383
- dpadLeft: { left: 6, top: 46 },
384
- dpadRight: { right: 6, top: 46 },
385
- axesGrid: {
386
- flexDirection: "row",
387
- flexWrap: "wrap",
388
- gap: 8,
747
+ psStickCenterPressed: {
748
+ borderColor: "#93c5fd",
749
+ backgroundColor: "#dbeafe",
389
750
  },
390
751
  });
@@ -1,5 +1,5 @@
1
- import { type JSX } from 'react';
2
- import type { AxisEvent, ButtonEvent, DpadEvent, GamepadButtonName, StickAxisName, StatusEvent } from './types';
1
+ import { type JSX } from "react";
2
+ import type { AxisEvent, ButtonEvent, DpadEvent, GamepadButtonName, StickAxisName, StatusEvent } from "./types";
3
3
  type Options = {
4
4
  enabled?: boolean;
5
5
  axisThreshold?: number;
@@ -11,6 +11,7 @@ type Options = {
11
11
  type Return = {
12
12
  pressedButtons: Set<GamepadButtonName>;
13
13
  axes: Partial<Record<StickAxisName, number>>;
14
+ buttonValues: Partial<Record<GamepadButtonName, number>>;
14
15
  isPressed: (key: GamepadButtonName) => boolean;
15
16
  bridge: JSX.Element | null;
16
17
  };
@@ -9,6 +9,7 @@ const react_1 = require("react");
9
9
  const GamepadBridge_1 = __importDefault(require("./GamepadBridge"));
10
10
  function useGamepad({ enabled = true, axisThreshold = 0.15, onButton, onAxis, onDpad, onStatus, } = {}) {
11
11
  const [pressedButtons, setPressedButtons] = (0, react_1.useState)(new Set());
12
+ const [buttonValues, setButtonValues] = (0, react_1.useState)({});
12
13
  const pressedRef = (0, react_1.useRef)(new Set());
13
14
  const [axes, setAxes] = (0, react_1.useState)({
14
15
  leftX: 0,
@@ -29,6 +30,10 @@ function useGamepad({ enabled = true, axisThreshold = 0.15, onButton, onAxis, on
29
30
  }
30
31
  pressedRef.current = next;
31
32
  setPressedButtons(next);
33
+ setButtonValues((prev) => ({
34
+ ...prev,
35
+ [event.button]: event.value,
36
+ }));
32
37
  onButton === null || onButton === void 0 ? void 0 : onButton(event);
33
38
  }, [onButton]);
34
39
  const handleAxis = (0, react_1.useCallback)((event) => {
@@ -45,9 +50,10 @@ function useGamepad({ enabled = true, axisThreshold = 0.15, onButton, onAxis, on
45
50
  }
46
51
  if (!enabled) {
47
52
  setAxes({ leftX: 0, leftY: 0, rightX: 0, rightY: 0 });
53
+ setButtonValues({});
48
54
  }
49
55
  }, [enabled]);
50
56
  const bridge = (0, react_1.useMemo)(() => ((0, jsx_runtime_1.jsx)(GamepadBridge_1.default, { enabled: enabled, axisThreshold: axisThreshold, onDpad: handleDpad, onButton: handleButton, onAxis: handleAxis, onStatus: onStatus })), [enabled, axisThreshold, handleAxis, handleButton, handleDpad, onStatus]);
51
57
  const isPressed = (0, react_1.useCallback)((key) => pressedRef.current.has(key), []);
52
- return { pressedButtons, axes, isPressed, bridge };
58
+ return { pressedButtons, axes, buttonValues, isPressed, bridge };
53
59
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-earl-gamepad",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "React Native gamepad bridge via WebView (buttons, sticks, d-pad, status).",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",