@rpg-engine/long-bow 0.7.53 → 0.7.61

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpg-engine/long-bow",
3
- "version": "0.7.53",
3
+ "version": "0.7.61",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "typings": "dist/index.d.ts",
@@ -87,7 +87,6 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
87
87
  onOutsideDrop,
88
88
  checkIfItemCanBeMoved,
89
89
  initialPosition,
90
- checkIfItemShouldDragEnd,
91
90
  scale,
92
91
  shortcuts,
93
92
  setItemShortcut,
@@ -191,7 +190,6 @@ export const ItemContainer: React.FC<IItemContainerProps> = ({
191
190
  onDragEnd={onDragEnd}
192
191
  dragScale={scale}
193
192
  checkIfItemCanBeMoved={checkIfItemCanBeMoved}
194
- checkIfItemShouldDragEnd={checkIfItemShouldDragEnd}
195
193
  openQuantitySelector={(maxQuantity, callback) => {
196
194
  setQuantitySelect({
197
195
  isOpen: true,
@@ -9,14 +9,14 @@ import {
9
9
  } from '@rpg-engine/shared';
10
10
 
11
11
  import { observer } from 'mobx-react-lite';
12
- import React, { useCallback, useEffect, useRef, useState } from 'react';
13
- import Draggable, { DraggableEventHandler } from 'react-draggable';
12
+ import React, { useCallback, useEffect, useState } from 'react';
13
+ import Draggable from 'react-draggable';
14
14
  import styled from 'styled-components';
15
15
  import { IPosition } from '../../../types/eventTypes';
16
16
  import { rarityColor } from './ItemSlotRarity';
17
17
  import { ItemSlotRenderer } from './ItemSlotRenderer';
18
18
  import { ItemSlotToolTips } from './ItemSlotTooltips';
19
- import { useDragging } from './context/DraggingContext';
19
+ import { useItemSlotDragAndDrop } from './hooks/useItemSlotDragAndDrop';
20
20
  import { IContextMenuItem, generateContextMenu } from './itemContainerHelper';
21
21
 
22
22
  export const EquipmentSlotSpriteByType: any = {
@@ -61,7 +61,6 @@ interface IProps {
61
61
  onOutsideDrop?: (item: IItem, position: IPosition) => void;
62
62
  dragScale?: number;
63
63
  checkIfItemCanBeMoved?: () => boolean;
64
- checkIfItemShouldDragEnd?: () => boolean;
65
64
  openQuantitySelector?: (maxQuantity: number, callback: () => void) => void;
66
65
  onPlaceDrop?: (
67
66
  item: IItem | null,
@@ -114,7 +113,6 @@ export const ItemSlot = React.memo(
114
113
  onOutsideDrop: onDrop,
115
114
  checkIfItemCanBeMoved,
116
115
  openQuantitySelector,
117
- checkIfItemShouldDragEnd,
118
116
  dragScale,
119
117
  isSelectingShortcut,
120
118
  equipmentSet,
@@ -131,31 +129,39 @@ export const ItemSlot = React.memo(
131
129
  visible: false,
132
130
  position: { x: 0, y: 0 },
133
131
  });
134
- const [dragState, setDragState] = useState<DragState>({
135
- isFocused: false,
136
- wasDragged: false,
137
- position: { x: 0, y: 0 },
138
- dropPosition: null,
139
- });
140
- const dragContainer = useRef<HTMLDivElement>(null);
141
- const { item: draggingItem, setDraggingItem } = useDragging();
132
+
142
133
  const [contextActions, setContextActions] = useState<IContextMenuItem[]>(
143
134
  []
144
135
  );
145
- const [touchStartTime, setTouchStartTime] = useState<number | null>(null);
146
- const [
147
- touchStartPosition,
148
- setTouchStartPosition,
149
- ] = useState<IPosition | null>(null);
150
- const [isDragging, setIsDragging] = useState<boolean>(false); // New state to track dragging
151
136
 
152
- useEffect(() => {
153
- setDragState(prev => ({
154
- ...prev,
155
- position: { x: 0, y: 0 },
156
- isFocused: false,
157
- }));
137
+ const {
138
+ dragContainer,
139
+ dragState,
140
+ draggingItem,
141
+ getContainerBounds,
142
+ onDraggableStart,
143
+ onDraggableProgress,
144
+ onDraggableStop,
145
+ } = useItemSlotDragAndDrop({
146
+ isDepotSystem: !!isDepotSystem,
147
+ item: item!,
148
+ onDrop: onDrop ?? (() => {}),
149
+ onDragEnd,
150
+ checkIfItemCanBeMoved,
151
+ setItemShortcut,
152
+ isSelectingShortcut,
153
+ onDragStart,
154
+ onPointerDown,
155
+ containerType: containerType!,
156
+ slotIndex,
157
+ openQuantitySelector: openQuantitySelector ?? (() => {}),
158
+ isContextMenuDisabled,
159
+ setTooltipState,
160
+ contextMenuState,
161
+ setContextMenuState,
162
+ });
158
163
 
164
+ useEffect(() => {
159
165
  if (item && containerType) {
160
166
  setContextActions(
161
167
  generateContextMenu(item, containerType, isDepotSystem)
@@ -163,293 +169,98 @@ export const ItemSlot = React.memo(
163
169
  }
164
170
  }, [item, isDepotSystem]);
165
171
 
166
- useEffect(() => {
167
- if (onDrop && item && dragState.dropPosition) {
168
- onDrop(item, dragState.dropPosition);
169
- setDragState(prev => ({
170
- ...prev,
171
- dropPosition: null,
172
- }));
173
- }
174
- }, [dragState.dropPosition]);
175
-
176
- const getContainerBounds = useCallback(() => {
177
- const container = dragContainer.current;
178
- if (!container) return { left: 0, top: 0, right: 0, bottom: 0 };
179
- const rect = container.getBoundingClientRect();
180
- return {
181
- left: rect.left,
182
- top: rect.top,
183
- right: window.innerWidth - rect.right,
184
- bottom: window.innerHeight - rect.bottom,
185
- };
186
- }, [dragContainer]);
187
-
188
- const resetItem = () => {
189
- setTooltipState(prev => ({
190
- ...prev,
191
- visible: false,
192
- mobileVisible: false,
193
- }));
194
- setDragState(prev => ({ ...prev, wasDragged: false }));
195
- setIsDragging(false); // Reset dragging flag
196
- };
197
-
198
- const onSuccessfulDrag = (quantity?: number) => {
199
- resetItem();
200
-
201
- if (quantity === -1) {
202
- setDragState(prev => ({
203
- ...prev,
204
- position: { x: 0, y: 0 },
205
- isFocused: false,
206
- }));
207
- } else if (item) {
208
- onDragEnd?.(quantity);
209
- }
210
- };
211
-
212
- const onDraggableStop: DraggableEventHandler = (e, data) => {
213
- setDraggingItem(null);
214
-
215
- const target = e.target as HTMLElement;
216
- if (!target) return;
217
-
218
- if (target?.id.includes('shortcutSetter') && setItemShortcut && item) {
219
- const index = parseInt(target.id.split('_')[1]);
220
- if (!isNaN(index)) {
221
- setItemShortcut(item, index);
222
- }
223
- }
224
-
225
- // Remove the class react-draggable-dragging from the element
226
- // to prevent the item from being dragged again
227
- target.classList.remove('react-draggable-dragging');
228
-
229
- const isTouch = e.type.startsWith('touch');
230
-
231
- if (isTouch) {
232
- const touchEvent = e as TouchEvent;
233
- const touch = touchEvent.changedTouches[0];
234
- const touchEndTime = new Date().getTime();
235
- const touchDuration = touchStartTime
236
- ? touchEndTime - touchStartTime
237
- : 0;
238
-
239
- // Check if it's a short tap (less than 200ms) and hasn't moved much
240
- const isShortTap = touchDuration < 200;
241
- const hasMovedSignificantly =
242
- touchStartPosition &&
243
- (Math.abs(touch.clientX - touchStartPosition.x) > 10 ||
244
- Math.abs(touch.clientY - touchStartPosition.y) > 10);
245
-
246
- if (isShortTap && !hasMovedSignificantly) {
247
- // Handle as a tap/click
248
- if (item) {
249
- setTooltipState(prev => ({
250
- ...prev,
251
- mobileVisible: true,
252
- }));
253
- onPointerDown(item.type, containerType ?? null, item);
254
- }
255
- return;
256
- }
257
- }
258
-
259
- // Threshold for considering a tap/click as a drag
260
- const dragThreshold = 5; // pixels
261
- const isDrag =
262
- Math.abs(data.x) > dragThreshold || Math.abs(data.y) > dragThreshold;
263
-
264
- if (dragState.wasDragged && item && !isSelectingShortcut) {
265
- //@ts-ignore
266
- const classes: string[] = Array.from(e.target?.classList);
267
-
268
- const isOutsideDrop =
269
- classes.some(elm => {
270
- return elm.includes('rpgui-content');
271
- }) || classes.length === 0;
272
-
273
- if (isOutsideDrop) {
274
- setDragState(prev => ({
275
- ...prev,
276
- dropPosition: { x: data.x, y: data.y },
277
- }));
278
- }
279
-
280
- setDragState(prev => ({ ...prev, wasDragged: false }));
281
-
282
- const target = dragContainer.current;
283
- if (!target || !dragState.wasDragged) return;
172
+ const bounds = getContainerBounds();
284
173
 
285
- const style = window.getComputedStyle(target);
286
- const matrix = new DOMMatrixReadOnly(style.transform);
287
- const x = matrix.m41;
288
- const y = matrix.m42;
174
+ const handleInteraction = useCallback(
175
+ (event: React.MouseEvent | React.TouchEvent) => {
176
+ event.stopPropagation();
289
177
 
290
- setDragState(prev => ({
291
- ...prev,
292
- position: { x, y },
293
- }));
178
+ const { clientX, clientY } =
179
+ 'touches' in event ? event.touches[0] : event;
294
180
 
295
- setTimeout(() => {
296
- if (checkIfItemCanBeMoved && checkIfItemCanBeMoved()) {
297
- if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd())
298
- return;
299
- if (
300
- item.stackQty &&
301
- item.stackQty !== 1 &&
302
- openQuantitySelector
303
- ) {
304
- openQuantitySelector(item.stackQty, onSuccessfulDrag);
305
- } else onSuccessfulDrag(item.stackQty);
306
- } else {
307
- resetItem();
308
- setDragState(prev => ({
309
- ...prev,
310
- isFocused: false,
311
- position: { x: 0, y: 0 },
312
- }));
181
+ if (item && containerType) {
182
+ if (onPlaceDrop && draggingItem) {
183
+ onPlaceDrop(item, slotIndex, containerType);
313
184
  }
314
- }, 50);
315
- } else if (item) {
316
- if (isTouch && isDragging) {
317
- // If it's a touch and we were dragging, do not show context menu
318
- return;
319
- } else if (isTouch && !isDrag) {
320
- // Handle as a tap/click
321
- setTooltipState(prev => ({
322
- ...prev,
323
- mobileVisible: true,
324
- }));
325
- onPointerDown(item.type, containerType ?? null, item);
326
- } else if (
327
- !isTouch &&
328
- !isContextMenuDisabled &&
329
- !isSelectingShortcut
330
- ) {
331
- // Handle as context menu for mouse devices
332
- setContextMenuState(prev => ({
333
- ...prev,
334
- visible: !contextMenuState.visible,
335
- }));
336
-
337
- const event = e as MouseEvent;
338
-
339
- if (event.clientX && event.clientY) {
340
- setContextMenuState(prev => ({
341
- ...prev,
342
- position: {
343
- x: event.clientX - 10,
344
- y: event.clientY - 5,
345
- },
346
- }));
185
+ if (
186
+ onPointerDown &&
187
+ onDragStart === undefined &&
188
+ onDragEnd === undefined
189
+ ) {
190
+ onPointerDown(item.type, containerType, item);
347
191
  }
348
192
  }
349
193
 
350
- if (!isDrag && !isTouch) {
351
- console.log('Calling onPointerDown');
352
- onPointerDown(item.type, containerType ?? null, item);
353
- }
354
- }
355
-
356
- setDragState(prev => ({ ...prev, wasDragged: false }));
357
- console.log('Final dragState:', dragState);
358
- };
359
-
360
- const onDraggableStart: DraggableEventHandler = () => {
361
- if (!item || isSelectingShortcut) {
362
- return;
363
- }
364
-
365
- if (onDragStart && containerType) {
366
- onDragStart(item, slotIndex, containerType);
367
- }
368
-
369
- setIsDragging(true); // Set dragging flag on start
370
- };
371
-
372
- const onDraggableProgress: DraggableEventHandler = (_e, data) => {
373
- if (
374
- Math.abs(data.x - dragState.position.x) > 5 ||
375
- Math.abs(data.y - dragState.position.y) > 5
376
- ) {
377
- setDragState(prev => ({
378
- ...prev,
379
- wasDragged: true,
380
- isFocused: true,
381
- }));
382
- }
383
-
384
- if (!draggingItem) {
385
- setDraggingItem(item);
386
- }
387
- };
388
-
389
- const onTouchStartHandler = (e: React.TouchEvent) => {
390
- setTouchStartTime(new Date().getTime());
391
- setTouchStartPosition({
392
- x: e.touches[0].clientX,
393
- y: e.touches[0].clientY,
394
- });
395
- };
396
-
397
- const onTouchEndHandler = (e: React.TouchEvent) => {
398
- // Only prevent default if not dragging
399
- if (!isDragging) {
400
- e.preventDefault();
401
- }
402
-
403
- const touch = e.changedTouches[0];
404
- const touchEndTime = new Date().getTime();
405
- const touchDuration = touchStartTime
406
- ? touchEndTime - touchStartTime
407
- : 0;
408
-
409
- // Check if it's a short tap (less than 200ms) and hasn't moved much
410
- const isShortTap = touchDuration < 200;
411
- const hasMovedSignificantly =
412
- touchStartPosition &&
413
- (Math.abs(touch.clientX - touchStartPosition.x) > 10 ||
414
- Math.abs(touch.clientY - touchStartPosition.y) > 10);
194
+ setTooltipState(prev => ({ ...prev, visible: true }));
195
+ onMouseOver?.(event, slotIndex, item, clientX, clientY);
196
+ },
197
+ [
198
+ item,
199
+ containerType,
200
+ slotIndex,
201
+ onPlaceDrop,
202
+ onPointerDown,
203
+ onMouseOver,
204
+ onDragStart,
205
+ onDragEnd,
206
+ ]
207
+ );
415
208
 
416
- if (isShortTap && !hasMovedSignificantly) {
417
- // Handle as a tap/click
418
- if (item) {
419
- setTooltipState(prev => ({
420
- ...prev,
421
- mobileVisible: true,
422
- }));
423
- onPointerDown(item.type, containerType ?? null, item);
209
+ const handleInteractionEnd = useCallback(
210
+ (event: React.MouseEvent | React.TouchEvent) => {
211
+ event.preventDefault();
212
+ event.stopPropagation();
213
+
214
+ setTooltipState(prev => ({ ...prev, visible: false }));
215
+ onMouseOut?.();
216
+
217
+ if (event.type === 'touchend' && 'changedTouches' in event) {
218
+ const { clientX, clientY } = event.changedTouches[0];
219
+ const simulatedEvent = new MouseEvent('mouseup', {
220
+ clientX,
221
+ clientY,
222
+ bubbles: true,
223
+ });
224
+ document
225
+ .elementFromPoint(clientX, clientY)
226
+ ?.dispatchEvent(simulatedEvent);
424
227
  }
425
- } else {
426
- // Handle as a drag end
427
- const simulatedEvent = new MouseEvent('mouseup', {
428
- clientX: touch.clientX,
429
- clientY: touch.clientY,
430
- bubbles: true,
431
- });
432
-
433
- document
434
- .elementFromPoint(touch.clientX, touch.clientY)
435
- ?.dispatchEvent(simulatedEvent);
436
- }
437
-
438
- setIsDragging(false); // Reset dragging flag on touch end
439
- };
228
+ },
229
+ [onMouseOut]
230
+ );
440
231
 
441
- const bounds = getContainerBounds();
442
232
  return (
443
233
  <Container
444
234
  isDraggingItem={!!draggingItem}
445
235
  item={item}
446
236
  className="rpgui-icon empty-slot"
447
- onMouseUp={() => {
237
+ isSelectingShortcut={
238
+ isSelectingShortcut &&
239
+ (item?.type === ItemType.Consumable ||
240
+ item?.type === ItemType.Tool ||
241
+ item?.subType === ItemSubType.Seed)
242
+ }
243
+ onMouseDown={handleInteraction}
244
+ onTouchStart={handleInteraction}
245
+ onMouseUp={e => {
246
+ handleInteractionEnd(e);
448
247
  const data = item ? item : null;
449
- if (onPlaceDrop && containerType) {
248
+ if (onPlaceDrop && containerType && draggingItem) {
450
249
  onPlaceDrop(data, slotIndex, containerType);
451
250
  }
452
251
  }}
252
+ onTouchEnd={e => {
253
+ handleInteractionEnd(e);
254
+ const { clientX, clientY } = e.changedTouches[0];
255
+ const simulatedEvent = new MouseEvent('mouseup', {
256
+ clientX,
257
+ clientY,
258
+ bubbles: true,
259
+ });
260
+ document
261
+ .elementFromPoint(clientX, clientY)
262
+ ?.dispatchEvent(simulatedEvent);
263
+ }}
453
264
  onPointerDown={
454
265
  onDragStart !== undefined && onDragEnd !== undefined
455
266
  ? undefined
@@ -458,14 +269,7 @@ export const ItemSlot = React.memo(
458
269
  onPointerDown(item.type, containerType ?? null, item);
459
270
  }
460
271
  }
461
- isSelectingShortcut={
462
- isSelectingShortcut &&
463
- (item?.type === ItemType.Consumable ||
464
- item?.type === ItemType.Tool ||
465
- item?.subType === ItemSubType.Seed)
466
- }
467
- onTouchStart={onTouchStartHandler}
468
- onTouchEnd={onTouchEndHandler}
272
+ onMouseLeave={handleInteractionEnd}
469
273
  >
470
274
  <Draggable
471
275
  axis={isSelectingShortcut ? 'none' : 'both'}
@@ -491,9 +295,7 @@ export const ItemSlot = React.memo(
491
295
  event.clientY
492
296
  );
493
297
  }}
494
- onMouseOut={() => {
495
- if (onMouseOut) onMouseOut();
496
- }}
298
+ onMouseOut={onMouseOut}
497
299
  onMouseEnter={() => {
498
300
  setTooltipState(prev => ({ ...prev, visible: true }));
499
301
  }}
@@ -595,6 +397,5 @@ const ItemContainer = styled.div<{ isFocused?: boolean }>`
595
397
  height: 64px;
596
398
 
597
399
  position: relative;
598
- ${({ isFocused }) =>
599
- isFocused ? 'z-index: 100; pointer-events: none;' : ''};
400
+ ${props => props.isFocused && 'z-index: 100; pointer-events: none;'};
600
401
  `;