@rpg-engine/long-bow 0.7.98 → 0.7.99
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/components/DPad/JoystickDPad.d.ts +2 -2
- package/dist/long-bow.cjs.development.js +206 -178
- package/dist/long-bow.cjs.development.js.map +1 -1
- package/dist/long-bow.cjs.production.min.js +1 -1
- package/dist/long-bow.cjs.production.min.js.map +1 -1
- package/dist/long-bow.esm.js +207 -179
- package/dist/long-bow.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DPad/JoystickDPad.tsx +270 -171
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
1
|
+
import React, { memo, useCallback, useEffect, useRef, useState } from 'react';
|
|
2
2
|
import styled from 'styled-components';
|
|
3
3
|
|
|
4
4
|
interface IDPadContainerProps {
|
|
@@ -8,6 +8,12 @@ interface IDPadContainerProps {
|
|
|
8
8
|
disabled?: boolean;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
interface IDPadButtonProps {
|
|
12
|
+
size?: number;
|
|
13
|
+
isPressed?: boolean;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
interface IDPadOptions {
|
|
12
18
|
/** Opacity of the entire component (0-1) */
|
|
13
19
|
opacity?: number;
|
|
@@ -28,173 +34,8 @@ interface IDPadProps {
|
|
|
28
34
|
options?: IDPadOptions;
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
disabled = false,
|
|
34
|
-
options = {},
|
|
35
|
-
}: IDPadProps): JSX.Element => {
|
|
36
|
-
const {
|
|
37
|
-
opacity = 1,
|
|
38
|
-
showBackground = false,
|
|
39
|
-
size = 100,
|
|
40
|
-
pressInterval = 500,
|
|
41
|
-
} = options;
|
|
42
|
-
|
|
43
|
-
const [pressedButtons, setPressedButtons] = useState<Set<string>>(new Set());
|
|
44
|
-
const intervalRef = useRef<number | null>(null);
|
|
45
|
-
const activeDirectionRef = useRef<'up' | 'down' | 'left' | 'right' | null>(
|
|
46
|
-
null
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
const clearPressInterval = useCallback(() => {
|
|
50
|
-
if (intervalRef.current) {
|
|
51
|
-
window.clearInterval(intervalRef.current);
|
|
52
|
-
intervalRef.current = null;
|
|
53
|
-
}
|
|
54
|
-
activeDirectionRef.current = null;
|
|
55
|
-
}, []);
|
|
56
|
-
|
|
57
|
-
const handleDirectionPress = useCallback(
|
|
58
|
-
(direction: 'up' | 'down' | 'left' | 'right') => {
|
|
59
|
-
if (disabled) return;
|
|
60
|
-
|
|
61
|
-
// Clear any existing interval
|
|
62
|
-
clearPressInterval();
|
|
63
|
-
|
|
64
|
-
// Set the active direction
|
|
65
|
-
activeDirectionRef.current = direction;
|
|
66
|
-
setPressedButtons(prev => new Set(prev).add(direction));
|
|
67
|
-
|
|
68
|
-
// Trigger first press immediately
|
|
69
|
-
onDirectionPress?.(direction);
|
|
70
|
-
|
|
71
|
-
// Set up the interval for continuous press
|
|
72
|
-
intervalRef.current = window.setInterval(() => {
|
|
73
|
-
if (activeDirectionRef.current === direction) {
|
|
74
|
-
onDirectionPress?.(direction);
|
|
75
|
-
}
|
|
76
|
-
}, pressInterval);
|
|
77
|
-
},
|
|
78
|
-
[disabled, onDirectionPress, pressInterval, clearPressInterval]
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const handleDirectionRelease = useCallback(
|
|
82
|
-
(direction: 'up' | 'down' | 'left' | 'right') => {
|
|
83
|
-
setPressedButtons(prev => {
|
|
84
|
-
const next = new Set(prev);
|
|
85
|
-
next.delete(direction);
|
|
86
|
-
return next;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
if (activeDirectionRef.current === direction) {
|
|
90
|
-
clearPressInterval();
|
|
91
|
-
}
|
|
92
|
-
},
|
|
93
|
-
[clearPressInterval]
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
// Cleanup on unmount
|
|
97
|
-
useEffect(() => {
|
|
98
|
-
return () => {
|
|
99
|
-
clearPressInterval();
|
|
100
|
-
};
|
|
101
|
-
}, [clearPressInterval]);
|
|
102
|
-
|
|
103
|
-
const preventDefault = (e: React.MouseEvent | React.TouchEvent) => {
|
|
104
|
-
e.preventDefault();
|
|
105
|
-
e.stopPropagation();
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<DPadContainer
|
|
110
|
-
opacity={opacity}
|
|
111
|
-
showBackground={showBackground}
|
|
112
|
-
size={size}
|
|
113
|
-
disabled={disabled}
|
|
114
|
-
onContextMenu={preventDefault}
|
|
115
|
-
>
|
|
116
|
-
<DPadButton
|
|
117
|
-
className="up"
|
|
118
|
-
onMouseDown={() => handleDirectionPress('up')}
|
|
119
|
-
onMouseUp={() => handleDirectionRelease('up')}
|
|
120
|
-
onMouseLeave={() => handleDirectionRelease('up')}
|
|
121
|
-
onTouchStart={() => handleDirectionPress('up')}
|
|
122
|
-
onTouchEnd={() => handleDirectionRelease('up')}
|
|
123
|
-
onContextMenu={preventDefault}
|
|
124
|
-
size={size}
|
|
125
|
-
isPressed={pressedButtons.has('up')}
|
|
126
|
-
disabled={disabled}
|
|
127
|
-
/>
|
|
128
|
-
<DPadButton
|
|
129
|
-
className="right"
|
|
130
|
-
onMouseDown={() => handleDirectionPress('right')}
|
|
131
|
-
onMouseUp={() => handleDirectionRelease('right')}
|
|
132
|
-
onMouseLeave={() => handleDirectionRelease('right')}
|
|
133
|
-
onTouchStart={() => handleDirectionPress('right')}
|
|
134
|
-
onTouchEnd={() => handleDirectionRelease('right')}
|
|
135
|
-
onContextMenu={preventDefault}
|
|
136
|
-
size={size}
|
|
137
|
-
isPressed={pressedButtons.has('right')}
|
|
138
|
-
disabled={disabled}
|
|
139
|
-
/>
|
|
140
|
-
<DPadButton
|
|
141
|
-
className="down"
|
|
142
|
-
onMouseDown={() => handleDirectionPress('down')}
|
|
143
|
-
onMouseUp={() => handleDirectionRelease('down')}
|
|
144
|
-
onMouseLeave={() => handleDirectionRelease('down')}
|
|
145
|
-
onTouchStart={() => handleDirectionPress('down')}
|
|
146
|
-
onTouchEnd={() => handleDirectionRelease('down')}
|
|
147
|
-
onContextMenu={preventDefault}
|
|
148
|
-
size={size}
|
|
149
|
-
isPressed={pressedButtons.has('down')}
|
|
150
|
-
disabled={disabled}
|
|
151
|
-
/>
|
|
152
|
-
<DPadButton
|
|
153
|
-
className="left"
|
|
154
|
-
onMouseDown={() => handleDirectionPress('left')}
|
|
155
|
-
onMouseUp={() => handleDirectionRelease('left')}
|
|
156
|
-
onMouseLeave={() => handleDirectionRelease('left')}
|
|
157
|
-
onTouchStart={() => handleDirectionPress('left')}
|
|
158
|
-
onTouchEnd={() => handleDirectionRelease('left')}
|
|
159
|
-
onContextMenu={preventDefault}
|
|
160
|
-
size={size}
|
|
161
|
-
isPressed={pressedButtons.has('left')}
|
|
162
|
-
disabled={disabled}
|
|
163
|
-
/>
|
|
164
|
-
<DPadCenter
|
|
165
|
-
size={size}
|
|
166
|
-
disabled={disabled}
|
|
167
|
-
onContextMenu={preventDefault}
|
|
168
|
-
/>
|
|
169
|
-
</DPadContainer>
|
|
170
|
-
);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const DPadContainer = styled.div<IDPadContainerProps>`
|
|
174
|
-
width: ${props => props.size ?? 100}px;
|
|
175
|
-
height: ${props => props.size ?? 100}px;
|
|
176
|
-
position: relative;
|
|
177
|
-
background: ${props => (props.showBackground ? '#b8b8b8' : 'transparent')};
|
|
178
|
-
border-radius: 50%;
|
|
179
|
-
box-shadow: ${props =>
|
|
180
|
-
props.showBackground
|
|
181
|
-
? 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2)'
|
|
182
|
-
: 'none'};
|
|
183
|
-
opacity: ${props => (props.disabled ? 0.5 : props.opacity ?? 1)};
|
|
184
|
-
user-select: none;
|
|
185
|
-
cursor: ${props => (props.disabled ? 'not-allowed' : 'default')};
|
|
186
|
-
transition: opacity 0.2s ease;
|
|
187
|
-
touch-action: none;
|
|
188
|
-
-webkit-tap-highlight-color: transparent;
|
|
189
|
-
`;
|
|
190
|
-
|
|
191
|
-
interface IDPadButtonProps {
|
|
192
|
-
size?: number;
|
|
193
|
-
isPressed?: boolean;
|
|
194
|
-
disabled?: boolean;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const DPadButton = styled.div<IDPadButtonProps>`
|
|
37
|
+
// Memoize the styled components since they don't depend on props that change frequently
|
|
38
|
+
const DPadButton = memo(styled.div<IDPadButtonProps>`
|
|
198
39
|
position: absolute;
|
|
199
40
|
background: ${props => (props.isPressed ? '#363636' : '#424242')};
|
|
200
41
|
box-shadow: ${props =>
|
|
@@ -285,9 +126,9 @@ const DPadButton = styled.div<IDPadButtonProps>`
|
|
|
285
126
|
transform: translate(50%, -50%);
|
|
286
127
|
}
|
|
287
128
|
}
|
|
288
|
-
|
|
129
|
+
`);
|
|
289
130
|
|
|
290
|
-
const DPadCenter = styled.div<IDPadButtonProps>`
|
|
131
|
+
const DPadCenter = memo(styled.div<IDPadButtonProps>`
|
|
291
132
|
position: absolute;
|
|
292
133
|
width: ${props => (props.size ?? 100) * 0.3}px;
|
|
293
134
|
height: ${props => (props.size ?? 100) * 0.3}px;
|
|
@@ -315,4 +156,262 @@ const DPadCenter = styled.div<IDPadButtonProps>`
|
|
|
315
156
|
pointer-events: none;
|
|
316
157
|
box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.8);
|
|
317
158
|
}
|
|
318
|
-
|
|
159
|
+
`);
|
|
160
|
+
|
|
161
|
+
const DPadContainer = memo(styled.div<IDPadContainerProps>`
|
|
162
|
+
width: ${props => props.size ?? 100}px;
|
|
163
|
+
height: ${props => props.size ?? 100}px;
|
|
164
|
+
position: relative;
|
|
165
|
+
background: ${props => (props.showBackground ? '#b8b8b8' : 'transparent')};
|
|
166
|
+
border-radius: 50%;
|
|
167
|
+
box-shadow: ${props =>
|
|
168
|
+
props.showBackground
|
|
169
|
+
? 'inset 0 0 10px rgba(0, 0, 0, 0.3), 0 4px 8px rgba(0, 0, 0, 0.3), 0 2px 4px rgba(0, 0, 0, 0.2)'
|
|
170
|
+
: 'none'};
|
|
171
|
+
opacity: ${props => (props.disabled ? 0.5 : props.opacity ?? 1)};
|
|
172
|
+
user-select: none;
|
|
173
|
+
cursor: ${props => (props.disabled ? 'not-allowed' : 'default')};
|
|
174
|
+
transition: opacity 0.2s ease;
|
|
175
|
+
touch-action: none;
|
|
176
|
+
-webkit-tap-highlight-color: transparent;
|
|
177
|
+
`);
|
|
178
|
+
|
|
179
|
+
export const JoystickDPad = memo(
|
|
180
|
+
({
|
|
181
|
+
onDirectionPress,
|
|
182
|
+
disabled = false,
|
|
183
|
+
options = {},
|
|
184
|
+
}: IDPadProps): JSX.Element => {
|
|
185
|
+
const {
|
|
186
|
+
opacity = 1,
|
|
187
|
+
showBackground = false,
|
|
188
|
+
size = 100,
|
|
189
|
+
pressInterval = 500,
|
|
190
|
+
} = options;
|
|
191
|
+
|
|
192
|
+
// Use refs for values that don't need to trigger re-renders
|
|
193
|
+
const [pressedButtons, setPressedButtons] = useState<Set<string>>(
|
|
194
|
+
new Set()
|
|
195
|
+
);
|
|
196
|
+
const intervalRef = useRef<number | null>(null);
|
|
197
|
+
const activeDirectionRef = useRef<'up' | 'down' | 'left' | 'right' | null>(
|
|
198
|
+
null
|
|
199
|
+
);
|
|
200
|
+
const touchStartRef = useRef<{ x: number; y: number } | null>(null);
|
|
201
|
+
const isPressedRef = useRef<boolean>(false);
|
|
202
|
+
|
|
203
|
+
const clearPressInterval = useCallback(() => {
|
|
204
|
+
if (intervalRef.current !== null) {
|
|
205
|
+
window.clearInterval(intervalRef.current);
|
|
206
|
+
intervalRef.current = null;
|
|
207
|
+
}
|
|
208
|
+
activeDirectionRef.current = null;
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const clearAllPresses = useCallback(() => {
|
|
212
|
+
clearPressInterval();
|
|
213
|
+
setPressedButtons(new Set());
|
|
214
|
+
activeDirectionRef.current = null;
|
|
215
|
+
isPressedRef.current = false;
|
|
216
|
+
}, [clearPressInterval]);
|
|
217
|
+
|
|
218
|
+
const handleDirectionPress = useCallback(
|
|
219
|
+
(direction: 'up' | 'down' | 'left' | 'right') => {
|
|
220
|
+
if (disabled) return;
|
|
221
|
+
|
|
222
|
+
// Clear any existing presses first
|
|
223
|
+
clearAllPresses();
|
|
224
|
+
|
|
225
|
+
// Set new direction
|
|
226
|
+
activeDirectionRef.current = direction;
|
|
227
|
+
isPressedRef.current = true;
|
|
228
|
+
setPressedButtons(new Set([direction]));
|
|
229
|
+
onDirectionPress?.(direction);
|
|
230
|
+
|
|
231
|
+
intervalRef.current = window.setInterval(() => {
|
|
232
|
+
if (activeDirectionRef.current === direction) {
|
|
233
|
+
onDirectionPress?.(direction);
|
|
234
|
+
} else {
|
|
235
|
+
clearPressInterval();
|
|
236
|
+
}
|
|
237
|
+
}, pressInterval);
|
|
238
|
+
},
|
|
239
|
+
[
|
|
240
|
+
disabled,
|
|
241
|
+
onDirectionPress,
|
|
242
|
+
pressInterval,
|
|
243
|
+
clearPressInterval,
|
|
244
|
+
clearAllPresses,
|
|
245
|
+
]
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const handleDirectionRelease = useCallback(
|
|
249
|
+
(direction: 'up' | 'down' | 'left' | 'right') => {
|
|
250
|
+
if (activeDirectionRef.current === direction) {
|
|
251
|
+
clearAllPresses();
|
|
252
|
+
}
|
|
253
|
+
},
|
|
254
|
+
[clearAllPresses]
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const handleTouchStart = useCallback(
|
|
258
|
+
(e: React.TouchEvent, direction: 'up' | 'down' | 'left' | 'right') => {
|
|
259
|
+
const touch = e.touches[0];
|
|
260
|
+
if (touch) {
|
|
261
|
+
touchStartRef.current = { x: touch.clientX, y: touch.clientY };
|
|
262
|
+
handleDirectionPress(direction);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
[handleDirectionPress]
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const handleTouchMove = useCallback(
|
|
269
|
+
(e: React.TouchEvent) => {
|
|
270
|
+
const touch = e.touches[0];
|
|
271
|
+
if (!touch || !touchStartRef.current) return;
|
|
272
|
+
|
|
273
|
+
const { x: startX, y: startY } = touchStartRef.current;
|
|
274
|
+
const deltaX = touch.clientX - startX;
|
|
275
|
+
const deltaY = touch.clientY - startY;
|
|
276
|
+
|
|
277
|
+
// Calculate angle and distance
|
|
278
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
279
|
+
const angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
|
280
|
+
|
|
281
|
+
// Only trigger if we've moved enough
|
|
282
|
+
const threshold = size * 0.15; // Adaptive threshold based on d-pad size
|
|
283
|
+
if (distance < threshold) return;
|
|
284
|
+
|
|
285
|
+
let newDirection: 'up' | 'down' | 'left' | 'right' | null = null;
|
|
286
|
+
|
|
287
|
+
// Determine direction based on angle
|
|
288
|
+
if (angle > -45 && angle <= 45) newDirection = 'right';
|
|
289
|
+
else if (angle > 45 && angle <= 135) newDirection = 'down';
|
|
290
|
+
else if (angle > 135 || angle <= -135) newDirection = 'left';
|
|
291
|
+
else if (angle > -135 && angle <= -45) newDirection = 'up';
|
|
292
|
+
|
|
293
|
+
if (newDirection && newDirection !== activeDirectionRef.current) {
|
|
294
|
+
handleDirectionPress(newDirection);
|
|
295
|
+
// Update touch start to current position to prevent jitter
|
|
296
|
+
touchStartRef.current = { x: touch.clientX, y: touch.clientY };
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
[handleDirectionPress, size]
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
// Add a new cleanup function for touch events
|
|
303
|
+
const cleanupTouchEvents = useCallback(() => {
|
|
304
|
+
touchStartRef.current = null;
|
|
305
|
+
if (activeDirectionRef.current) {
|
|
306
|
+
handleDirectionRelease(activeDirectionRef.current);
|
|
307
|
+
}
|
|
308
|
+
}, [handleDirectionRelease]);
|
|
309
|
+
|
|
310
|
+
// Enhance the touch end handler
|
|
311
|
+
const handleTouchEnd = useCallback(() => {
|
|
312
|
+
cleanupTouchEvents();
|
|
313
|
+
}, [cleanupTouchEvents]);
|
|
314
|
+
|
|
315
|
+
// Add touch cancel handler
|
|
316
|
+
const handleTouchCancel = useCallback(() => {
|
|
317
|
+
cleanupTouchEvents();
|
|
318
|
+
}, [cleanupTouchEvents]);
|
|
319
|
+
|
|
320
|
+
// Enhance cleanup effect
|
|
321
|
+
useEffect(() => {
|
|
322
|
+
if (disabled) {
|
|
323
|
+
clearAllPresses();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const handleBlur = () => {
|
|
327
|
+
clearAllPresses();
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const handleVisibilityChange = () => {
|
|
331
|
+
if (document.hidden) {
|
|
332
|
+
clearAllPresses();
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const handlePointerUp = () => {
|
|
337
|
+
// Global pointer up as fallback for stuck buttons
|
|
338
|
+
if (isPressedRef.current) {
|
|
339
|
+
clearAllPresses();
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
window.addEventListener('blur', handleBlur);
|
|
344
|
+
window.addEventListener('pointerup', handlePointerUp);
|
|
345
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
346
|
+
|
|
347
|
+
return () => {
|
|
348
|
+
clearAllPresses();
|
|
349
|
+
window.removeEventListener('blur', handleBlur);
|
|
350
|
+
window.removeEventListener('pointerup', handlePointerUp);
|
|
351
|
+
document.removeEventListener(
|
|
352
|
+
'visibilitychange',
|
|
353
|
+
handleVisibilityChange
|
|
354
|
+
);
|
|
355
|
+
};
|
|
356
|
+
}, [disabled, clearAllPresses]);
|
|
357
|
+
|
|
358
|
+
// Memoize the preventDefault handler
|
|
359
|
+
const preventDefault = useCallback(
|
|
360
|
+
(e: React.MouseEvent | React.TouchEvent) => {
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
e.stopPropagation();
|
|
363
|
+
},
|
|
364
|
+
[]
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Memoize button props to prevent unnecessary re-renders
|
|
368
|
+
const buttonProps = useCallback(
|
|
369
|
+
(direction: 'up' | 'down' | 'left' | 'right') => ({
|
|
370
|
+
onMouseDown: () => handleDirectionPress(direction),
|
|
371
|
+
onMouseUp: () => handleDirectionRelease(direction),
|
|
372
|
+
onMouseLeave: () => handleDirectionRelease(direction),
|
|
373
|
+
onTouchStart: (e: React.TouchEvent) => handleTouchStart(e, direction),
|
|
374
|
+
onTouchMove: handleTouchMove,
|
|
375
|
+
onTouchEnd: handleTouchEnd,
|
|
376
|
+
onContextMenu: preventDefault,
|
|
377
|
+
size,
|
|
378
|
+
isPressed: pressedButtons.has(direction),
|
|
379
|
+
disabled,
|
|
380
|
+
}),
|
|
381
|
+
[
|
|
382
|
+
handleDirectionPress,
|
|
383
|
+
handleDirectionRelease,
|
|
384
|
+
handleTouchStart,
|
|
385
|
+
handleTouchMove,
|
|
386
|
+
handleTouchEnd,
|
|
387
|
+
preventDefault,
|
|
388
|
+
size,
|
|
389
|
+
pressedButtons,
|
|
390
|
+
disabled,
|
|
391
|
+
]
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
return (
|
|
395
|
+
<DPadContainer
|
|
396
|
+
opacity={opacity}
|
|
397
|
+
showBackground={showBackground}
|
|
398
|
+
size={size}
|
|
399
|
+
disabled={disabled}
|
|
400
|
+
onContextMenu={preventDefault}
|
|
401
|
+
onTouchCancel={handleTouchCancel}
|
|
402
|
+
>
|
|
403
|
+
<DPadButton className="up" {...buttonProps('up')} />
|
|
404
|
+
<DPadButton className="right" {...buttonProps('right')} />
|
|
405
|
+
<DPadButton className="down" {...buttonProps('down')} />
|
|
406
|
+
<DPadButton className="left" {...buttonProps('left')} />
|
|
407
|
+
<DPadCenter
|
|
408
|
+
size={size}
|
|
409
|
+
disabled={disabled}
|
|
410
|
+
onContextMenu={preventDefault}
|
|
411
|
+
/>
|
|
412
|
+
</DPadContainer>
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
JoystickDPad.displayName = 'JoystickDPad';
|