react-native-earl-gamepad 0.4.1 → 0.5.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
@@ -45,6 +45,50 @@ export function Controls() {
45
45
  }
46
46
  ```
47
47
 
48
+ ### Control Logic Example
49
+
50
+ Here is an example of mapping D-pad events to movement vectors
51
+
52
+ ```tsx
53
+ import { useState, useCallback } from "react";
54
+ import { GamepadBridge, type DpadEvent } from "react-native-earl-gamepad";
55
+ type MoveKey = keyof typeof MOVES;
56
+
57
+ const MOVES: Record<string, [number, number]> = {
58
+ up: [1, 0],
59
+ down: [-1, 0],
60
+ right: [0, 1],
61
+ left: [0, -1],
62
+ stop: [0, 0],
63
+
64
+ axis_left_x_neg: [0, -1],
65
+ axis_left_x_pos: [0, 1],
66
+ axis_left_y_pos: [1, 0],
67
+ axis_left_y_neg: [-1, 0],
68
+
69
+ // add more
70
+ }; // example only for the control logic
71
+
72
+ export function Controls() {
73
+ const [active, setActive] = useState<string | null>(null);
74
+
75
+ const handleDpad = useCallback(
76
+ (event: DpadEvent) => {
77
+ const key = event.key as MoveKey;
78
+ if (event.pressed) {
79
+ if (active !== key) {
80
+ console.log("Dpad press", key);
81
+ }
82
+ } else if (active === key) {
83
+ // do something
84
+ }
85
+ },
86
+ [active]
87
+ );
88
+ return <GamepadBridge enabled onDpad={handleDpad} axisThreshold={0.15} />;
89
+ }
90
+ ```
91
+
48
92
  ### Hook for stateful consumption
49
93
 
50
94
  `useGamepad` keeps pressed state and axes for you. You still need to render the provided `bridge` element once.
@@ -1,5 +1,5 @@
1
- import { StyleProp, ViewStyle } from 'react-native';
2
- import type { AxisEvent, ButtonEvent, DpadEvent, StatusEvent } from './types';
1
+ import { StyleProp, ViewStyle } from "react-native";
2
+ import type { AxisEvent, ButtonEvent, DpadEvent, StatusEvent } from "./types";
3
3
  type Props = {
4
4
  enabled?: boolean;
5
5
  axisThreshold?: number;
@@ -100,22 +100,34 @@ const buildBridgeHtml = (axisThreshold) => `
100
100
  </script>
101
101
  </body></html>`;
102
102
  function GamepadBridge({ enabled = true, axisThreshold = 0.15, onDpad, onButton, onAxis, onStatus, style, }) {
103
+ const webviewRef = (0, react_1.useRef)(null);
103
104
  const html = (0, react_1.useMemo)(() => buildBridgeHtml(axisThreshold), [axisThreshold]);
105
+ const focusBridge = (0, react_1.useCallback)(() => {
106
+ var _a, _b;
107
+ // On Android controllers, ensure the WebView is focusable so DPAD events feed navigator.getGamepads
108
+ const node = webviewRef.current;
109
+ (_a = node === null || node === void 0 ? void 0 : node.setNativeProps) === null || _a === void 0 ? void 0 : _a.call(node, { focusable: true, focusableInTouchMode: true });
110
+ (_b = node === null || node === void 0 ? void 0 : node.requestFocus) === null || _b === void 0 ? void 0 : _b.call(node);
111
+ }, []);
112
+ (0, react_1.useEffect)(() => {
113
+ if (enabled)
114
+ focusBridge();
115
+ }, [enabled, focusBridge]);
104
116
  if (!enabled)
105
117
  return null;
106
118
  const handleMessage = (event) => {
107
119
  try {
108
120
  const data = JSON.parse(event.nativeEvent.data);
109
- if (data.type === 'dpad') {
121
+ if (data.type === "dpad") {
110
122
  onDpad === null || onDpad === void 0 ? void 0 : onDpad(data);
111
123
  }
112
- else if (data.type === 'button') {
124
+ else if (data.type === "button") {
113
125
  onButton === null || onButton === void 0 ? void 0 : onButton(data);
114
126
  }
115
- else if (data.type === 'axis') {
127
+ else if (data.type === "axis") {
116
128
  onAxis === null || onAxis === void 0 ? void 0 : onAxis(data);
117
129
  }
118
- else if (data.type === 'status') {
130
+ else if (data.type === "status") {
119
131
  onStatus === null || onStatus === void 0 ? void 0 : onStatus(data);
120
132
  }
121
133
  }
@@ -123,5 +135,10 @@ function GamepadBridge({ enabled = true, axisThreshold = 0.15, onDpad, onButton,
123
135
  // ignore malformed messages
124
136
  }
125
137
  };
126
- return ((0, jsx_runtime_1.jsx)(react_native_webview_1.default, { source: { html }, originWhitelist: ['*'], onMessage: handleMessage, javaScriptEnabled: true, style: style !== null && style !== void 0 ? style : { width: 1, height: 1, position: 'absolute', opacity: 0 } }));
138
+ return ((0, jsx_runtime_1.jsx)(react_native_webview_1.default, { ref: webviewRef, source: { html }, originWhitelist: ["*"], onMessage: handleMessage, javaScriptEnabled: true, onLoad: focusBridge, onLayout: focusBridge, onTouchStart: focusBridge, style: style !== null && style !== void 0 ? style : {
139
+ width: 1,
140
+ height: 1,
141
+ position: "absolute",
142
+ opacity: 0,
143
+ } }));
127
144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-earl-gamepad",
3
- "version": "0.4.1",
3
+ "version": "0.5.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",