@rpg-engine/long-bow 0.7.52 → 0.7.60

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.52",
3
+ "version": "0.7.60",
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,30 +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
136
 
151
- useEffect(() => {
152
- setDragState(prev => ({
153
- ...prev,
154
- position: { x: 0, y: 0 },
155
- isFocused: false,
156
- }));
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
+ });
157
163
 
164
+ useEffect(() => {
158
165
  if (item && containerType) {
159
166
  setContextActions(
160
167
  generateContextMenu(item, containerType, isDepotSystem)
@@ -162,276 +169,98 @@ export const ItemSlot = React.memo(
162
169
  }
163
170
  }, [item, isDepotSystem]);
164
171
 
165
- useEffect(() => {
166
- if (onDrop && item && dragState.dropPosition) {
167
- onDrop(item, dragState.dropPosition);
168
- setDragState(prev => ({
169
- ...prev,
170
- dropPosition: null,
171
- }));
172
- }
173
- }, [dragState.dropPosition]);
174
-
175
- const getContainerBounds = useCallback(() => {
176
- const container = dragContainer.current;
177
- if (!container) return { left: 0, top: 0, right: 0, bottom: 0 };
178
- const rect = container.getBoundingClientRect();
179
- return {
180
- left: rect.left,
181
- top: rect.top,
182
- right: window.innerWidth - rect.right,
183
- bottom: window.innerHeight - rect.bottom,
184
- };
185
- }, [dragContainer]);
186
-
187
- const resetItem = () => {
188
- setTooltipState(prev => ({ ...prev, visible: false }));
189
- setDragState(prev => ({ ...prev, wasDragged: false }));
190
- };
191
-
192
- const onSuccessfulDrag = (quantity?: number) => {
193
- resetItem();
194
-
195
- if (quantity === -1) {
196
- setDragState(prev => ({
197
- ...prev,
198
- position: { x: 0, y: 0 },
199
- isFocused: false,
200
- }));
201
- } else if (item) {
202
- onDragEnd?.(quantity);
203
- }
204
- };
205
-
206
- const onDraggableStop: DraggableEventHandler = (e, data) => {
207
- setDraggingItem(null);
208
-
209
- const target = e.target as HTMLElement;
210
- if (!target) return;
211
-
212
- if (target?.id.includes('shortcutSetter') && setItemShortcut && item) {
213
- const index = parseInt(target.id.split('_')[1]);
214
- if (!isNaN(index)) {
215
- setItemShortcut(item, index);
216
- }
217
- }
218
-
219
- // remove the class react-draggable-dragging from the element
220
- // to prevent the item from being dragged again
221
- target.classList.remove('react-draggable-dragging');
222
-
223
- const isTouch = e.type.startsWith('touch');
224
-
225
- if (isTouch) {
226
- const touchEvent = e as TouchEvent;
227
- const touch = touchEvent.changedTouches[0];
228
- const touchEndTime = new Date().getTime();
229
- const touchDuration = touchStartTime
230
- ? touchEndTime - touchStartTime
231
- : 0;
232
-
233
- // Check if it's a short tap (less than 200ms) and hasn't moved much
234
- const isShortTap = touchDuration < 200;
235
- const hasMovedSignificantly =
236
- touchStartPosition &&
237
- (Math.abs(touch.clientX - touchStartPosition.x) > 10 ||
238
- Math.abs(touch.clientY - touchStartPosition.y) > 10);
239
-
240
- if (isShortTap && !hasMovedSignificantly) {
241
- // Handle as a tap/click
242
- if (item) {
243
- setTooltipState(prev => ({
244
- ...prev,
245
- mobileVisible: true,
246
- }));
247
- onPointerDown(item.type, containerType ?? null, item);
248
- }
249
- return;
250
- }
251
- }
252
-
253
- // Threshold for considering a tap/click as a drag
254
- const dragThreshold = 5; // pixels
255
- const isDrag =
256
- Math.abs(data.x) > dragThreshold || Math.abs(data.y) > dragThreshold;
257
-
258
- if (dragState.wasDragged && item && !isSelectingShortcut) {
259
- //@ts-ignore
260
- const classes: string[] = Array.from(e.target?.classList);
261
-
262
- const isOutsideDrop =
263
- classes.some(elm => {
264
- return elm.includes('rpgui-content');
265
- }) || classes.length === 0;
266
-
267
- if (isOutsideDrop) {
268
- setDragState(prev => ({
269
- ...prev,
270
- dropPosition: { x: data.x, y: data.y },
271
- }));
272
- }
273
-
274
- setDragState(prev => ({ ...prev, wasDragged: false }));
275
-
276
- const target = dragContainer.current;
277
- if (!target || !dragState.wasDragged) return;
172
+ const bounds = getContainerBounds();
278
173
 
279
- const style = window.getComputedStyle(target);
280
- const matrix = new DOMMatrixReadOnly(style.transform);
281
- const x = matrix.m41;
282
- const y = matrix.m42;
174
+ const handleInteraction = useCallback(
175
+ (event: React.MouseEvent | React.TouchEvent) => {
176
+ event.stopPropagation();
283
177
 
284
- setDragState(prev => ({
285
- ...prev,
286
- position: { x, y },
287
- }));
178
+ const { clientX, clientY } =
179
+ 'touches' in event ? event.touches[0] : event;
288
180
 
289
- setTimeout(() => {
290
- if (checkIfItemCanBeMoved && checkIfItemCanBeMoved()) {
291
- if (checkIfItemShouldDragEnd && !checkIfItemShouldDragEnd())
292
- return;
293
- if (
294
- item.stackQty &&
295
- item.stackQty !== 1 &&
296
- openQuantitySelector
297
- ) {
298
- openQuantitySelector(item.stackQty, onSuccessfulDrag);
299
- } else onSuccessfulDrag(item.stackQty);
300
- } else {
301
- resetItem();
302
- setDragState(prev => ({
303
- ...prev,
304
- isFocused: false,
305
- position: { x: 0, y: 0 },
306
- }));
181
+ if (item && containerType) {
182
+ if (onPlaceDrop && draggingItem) {
183
+ onPlaceDrop(item, slotIndex, containerType);
307
184
  }
308
- }, 50);
309
- } else if (item) {
310
- if (isTouch && !isDrag) {
311
- setTooltipState(prev => ({
312
- ...prev,
313
- mobileVisible: true,
314
- }));
315
- } else if (
316
- !isTouch &&
317
- !isContextMenuDisabled &&
318
- !isSelectingShortcut
319
- ) {
320
- setContextMenuState(prev => ({
321
- ...prev,
322
- visible: !contextMenuState.visible,
323
- }));
324
-
325
- const event = e as MouseEvent;
326
-
327
- if (event.clientX && event.clientY) {
328
- setContextMenuState(prev => ({
329
- ...prev,
330
- position: {
331
- x: event.clientX - 10,
332
- y: event.clientY - 5,
333
- },
334
- }));
185
+ if (
186
+ onPointerDown &&
187
+ onDragStart === undefined &&
188
+ onDragEnd === undefined
189
+ ) {
190
+ onPointerDown(item.type, containerType, item);
335
191
  }
336
192
  }
337
193
 
338
- if (!isDrag || !isTouch) {
339
- console.log('Calling onPointerDown');
340
- onPointerDown(item.type, containerType ?? null, item);
341
- }
342
- }
343
-
344
- setDragState(prev => ({ ...prev, wasDragged: false }));
345
- console.log('Final dragState:', dragState);
346
- };
347
-
348
- const onDraggableStart: DraggableEventHandler = () => {
349
- if (!item || isSelectingShortcut) {
350
- return;
351
- }
352
-
353
- if (onDragStart && containerType) {
354
- onDragStart(item, slotIndex, containerType);
355
- }
356
- };
357
-
358
- const onDraggableProgress: DraggableEventHandler = (_e, data) => {
359
- if (
360
- Math.abs(data.x - dragState.position.x) > 5 ||
361
- Math.abs(data.y - dragState.position.y) > 5
362
- ) {
363
- setDragState(prev => ({
364
- ...prev,
365
- wasDragged: true,
366
- isFocused: true,
367
- }));
368
- }
369
-
370
- if (!draggingItem) {
371
- setDraggingItem(item);
372
- }
373
- };
374
-
375
- const onTouchStart = (e: React.TouchEvent) => {
376
- setTouchStartTime(new Date().getTime());
377
- setTouchStartPosition({
378
- x: e.touches[0].clientX,
379
- y: e.touches[0].clientY,
380
- });
381
- };
382
-
383
- const onTouchEnd = (e: React.TouchEvent) => {
384
- // Prevent default to stop potential unwanted behaviors
385
- e.preventDefault();
386
-
387
- const touch = e.changedTouches[0];
388
- const touchEndTime = new Date().getTime();
389
- const touchDuration = touchStartTime
390
- ? touchEndTime - touchStartTime
391
- : 0;
392
-
393
- // Check if it's a short tap (less than 200ms) and hasn't moved much
394
- const isShortTap = touchDuration < 200;
395
- const hasMovedSignificantly =
396
- touchStartPosition &&
397
- (Math.abs(touch.clientX - touchStartPosition.x) > 10 ||
398
- 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
+ );
399
208
 
400
- if (isShortTap && !hasMovedSignificantly) {
401
- // Handle as a tap/click
402
- if (item) {
403
- setTooltipState(prev => ({
404
- ...prev,
405
- mobileVisible: true,
406
- }));
407
- 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);
408
227
  }
409
- } else {
410
- // Handle as a drag end
411
- const simulatedEvent = new MouseEvent('mouseup', {
412
- clientX: touch.clientX,
413
- clientY: touch.clientY,
414
- bubbles: true,
415
- });
416
-
417
- document
418
- .elementFromPoint(touch.clientX, touch.clientY)
419
- ?.dispatchEvent(simulatedEvent);
420
- }
421
- };
228
+ },
229
+ [onMouseOut]
230
+ );
422
231
 
423
- const bounds = getContainerBounds();
424
232
  return (
425
233
  <Container
426
234
  isDraggingItem={!!draggingItem}
427
235
  item={item}
428
236
  className="rpgui-icon empty-slot"
429
- 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);
430
247
  const data = item ? item : null;
431
- if (onPlaceDrop && containerType) {
248
+ if (onPlaceDrop && containerType && draggingItem) {
432
249
  onPlaceDrop(data, slotIndex, containerType);
433
250
  }
434
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
+ }}
435
264
  onPointerDown={
436
265
  onDragStart !== undefined && onDragEnd !== undefined
437
266
  ? undefined
@@ -440,14 +269,7 @@ export const ItemSlot = React.memo(
440
269
  onPointerDown(item.type, containerType ?? null, item);
441
270
  }
442
271
  }
443
- isSelectingShortcut={
444
- isSelectingShortcut &&
445
- (item?.type === ItemType.Consumable ||
446
- item?.type === ItemType.Tool ||
447
- item?.subType === ItemSubType.Seed)
448
- }
449
- onTouchStart={onTouchStart}
450
- onTouchEnd={onTouchEnd}
272
+ onMouseLeave={handleInteractionEnd}
451
273
  >
452
274
  <Draggable
453
275
  axis={isSelectingShortcut ? 'none' : 'both'}
@@ -473,9 +295,7 @@ export const ItemSlot = React.memo(
473
295
  event.clientY
474
296
  );
475
297
  }}
476
- onMouseOut={() => {
477
- if (onMouseOut) onMouseOut();
478
- }}
298
+ onMouseOut={onMouseOut}
479
299
  onMouseEnter={() => {
480
300
  setTooltipState(prev => ({ ...prev, visible: true }));
481
301
  }}