minecraft-inventory 0.1.23 → 0.1.24

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": "minecraft-inventory",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "release": {
@@ -310,7 +310,7 @@ export function InventoryOverlay({
310
310
  lineHeight: 1,
311
311
  }}
312
312
  >
313
- INV 0.1.23
313
+ INV 0.1.24
314
314
  </a>
315
315
  )}
316
316
 
@@ -96,6 +96,13 @@ export function Slot({
96
96
  }
97
97
  }, [label, item, renderSize])
98
98
 
99
+ // Clean up long press timer on unmount
100
+ useEffect(() => {
101
+ return () => {
102
+ if (longPressTimerRef.current) clearTimeout(longPressTimerRef.current)
103
+ }
104
+ }, [])
105
+
99
106
  const isHovered = hoveredSlot === index
100
107
  const isDragTarget = dragSlots.includes(index)
101
108
  const dragPreviewEntry = dragPreview.get(index)
@@ -247,19 +254,63 @@ export function Slot({
247
254
 
248
255
  // Mobile touch handlers
249
256
  const touchStartRef = useRef<{ x: number; y: number; time: number } | null>(null)
257
+ const longPressTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
258
+ const longPressFiredRef = useRef(false)
259
+
260
+ const cancelLongPress = useCallback(() => {
261
+ if (longPressTimerRef.current) {
262
+ clearTimeout(longPressTimerRef.current)
263
+ longPressTimerRef.current = null
264
+ }
265
+ }, [])
250
266
 
251
267
  const handleTouchStart = useCallback(
252
268
  (e: React.TouchEvent) => {
253
269
  if (!isMobile) return
254
270
  const touch = e.touches[0]
255
271
  touchStartRef.current = { x: touch.clientX, y: touch.clientY, time: Date.now() }
272
+ longPressFiredRef.current = false
273
+ cancelLongPress()
274
+ // Long press: open mobile menu after 400ms if item exists and no held item
275
+ if (item && !heldItem && !disabled) {
276
+ const startX = touch.clientX
277
+ const startY = touch.clientY
278
+ longPressTimerRef.current = setTimeout(() => {
279
+ longPressFiredRef.current = true
280
+ setMobileTouchPos({ x: startX, y: startY })
281
+ setMobileMenuOpen(true)
282
+ }, 400)
283
+ }
256
284
  },
257
- [isMobile],
285
+ [isMobile, item, heldItem, disabled, cancelLongPress],
286
+ )
287
+
288
+ const handleTouchMove = useCallback(
289
+ (e: React.TouchEvent) => {
290
+ if (!longPressTimerRef.current) return
291
+ const touch = e.touches[0]
292
+ const start = touchStartRef.current
293
+ if (!start) return
294
+ if (Math.abs(touch.clientX - start.x) > 10 || Math.abs(touch.clientY - start.y) > 10) {
295
+ cancelLongPress()
296
+ }
297
+ },
298
+ [cancelLongPress],
258
299
  )
259
300
 
260
301
  const handleTouchEnd = useCallback(
261
302
  (e: React.TouchEvent) => {
303
+ cancelLongPress()
262
304
  if (!isMobile || disabled) return
305
+ // If long press opened the menu, don't process the tap
306
+ if (longPressFiredRef.current) {
307
+ longPressFiredRef.current = false
308
+ e.stopPropagation()
309
+ e.preventDefault()
310
+ return
311
+ }
312
+ // If mobile menu is open, let menu buttons handle their own events
313
+ if (mobileMenuOpen) return
263
314
  const start = touchStartRef.current
264
315
  if (!start) return
265
316
  touchStartRef.current = null
@@ -297,7 +348,7 @@ export function Slot({
297
348
  setFocusedSlot(null)
298
349
  }
299
350
  },
300
- [isMobile, disabled, heldItem, sendAction, index, pKeyActive, setPKeyActive, focusedSlot, setFocusedSlot, onClickOverride],
351
+ [isMobile, disabled, heldItem, sendAction, index, pKeyActive, setPKeyActive, focusedSlot, setFocusedSlot, onClickOverride, cancelLongPress, mobileMenuOpen],
301
352
  )
302
353
 
303
354
  const handleMobilePickAll = useCallback(() => {
@@ -319,8 +370,16 @@ export function Slot({
319
370
  if (isNaN(amount) || amount <= 0) return
320
371
  setMobileMenuOpen(false)
321
372
  setShowTooltip(false)
322
- for (let i = 0; i < Math.min(amount, item.count); i++) {
323
- sendAction({ type: 'click', slotIndex: index, button: 'right', mode: 'normal' })
373
+ const take = Math.min(amount, item.count)
374
+ if (take >= item.count) {
375
+ // Take all: just left-click
376
+ sendAction({ type: 'click', slotIndex: index, button: 'left', mode: 'normal' })
377
+ } else {
378
+ // Pick up all, then put back (count - take) items one-by-one via right-click
379
+ sendAction({ type: 'click', slotIndex: index, button: 'left', mode: 'normal' })
380
+ for (let i = 0; i < item.count - take; i++) {
381
+ sendAction({ type: 'click', slotIndex: index, button: 'right', mode: 'normal' })
382
+ }
324
383
  }
325
384
  }, [item, sendAction, index])
326
385
 
@@ -368,6 +427,7 @@ export function Slot({
368
427
  onDoubleClick={handleDoubleClick}
369
428
  onContextMenu={handleContextMenu}
370
429
  onTouchStart={handleTouchStart}
430
+ onTouchMove={handleTouchMove}
371
431
  onTouchEnd={handleTouchEnd}
372
432
  aria-label={
373
433
  label ??
@@ -462,7 +522,12 @@ export function Slot({
462
522
 
463
523
  {isMobile && mobileMenuOpen && item && (
464
524
  <>
465
- <div className={styles.mobileOverlay} onClick={closeMobileMenu} />
525
+ <div
526
+ className={styles.mobileOverlay}
527
+ onClick={closeMobileMenu}
528
+ onTouchStart={(e) => e.stopPropagation()}
529
+ onTouchEnd={(e) => { e.stopPropagation(); e.preventDefault(); closeMobileMenu() }}
530
+ />
466
531
  <MobileSlotMenu
467
532
  item={item}
468
533
  x={mobileTouchPos.x}
@@ -509,10 +574,18 @@ function MobileSlotMenu({ item, x, y, onPickAll, onPickHalf, onPickCustom, onDro
509
574
  setPos({ left, top })
510
575
  }, [x, y])
511
576
 
577
+ // Wrapper to handle both touch and click, preventing event bubbling to the slot
578
+ const touchBtn = (handler: () => void) => ({
579
+ onTouchEnd: (e: React.TouchEvent) => { e.stopPropagation(); e.preventDefault(); handler() },
580
+ onClick: (e: React.MouseEvent) => { e.stopPropagation(); handler() },
581
+ })
582
+
512
583
  return (
513
584
  <div
514
585
  ref={menuRef}
515
586
  className={styles.mobileMenu}
587
+ onTouchStart={(e) => e.stopPropagation()}
588
+ onTouchEnd={(e) => e.stopPropagation()}
516
589
  style={{
517
590
  position: 'fixed',
518
591
  left: pos.left,
@@ -527,11 +600,11 @@ function MobileSlotMenu({ item, x, y, onPickAll, onPickHalf, onPickCustom, onDro
527
600
  <div className={styles.mobileMenuTitle}>
528
601
  {item.displayName ?? item.name ?? `Item #${item.type}`} ×{item.count}
529
602
  </div>
530
- <button className={styles.mobileBtn} onClick={onPickAll}>Take All ({item.count})</button>
531
- <button className={styles.mobileBtn} onClick={onPickHalf}>Take Half ({Math.ceil(item.count / 2)})</button>
532
- <button className={styles.mobileBtn} onClick={onPickCustom}>Take Amount…</button>
533
- <button className={[styles.mobileBtn, styles.mobileBtnDanger].join(' ')} onClick={onDrop}>Drop</button>
534
- <button className={styles.mobileBtn} onClick={onClose}>Cancel</button>
603
+ <button className={styles.mobileBtn} {...touchBtn(onPickAll)}>Take All ({item.count})</button>
604
+ <button className={styles.mobileBtn} {...touchBtn(onPickHalf)}>Take Half ({Math.ceil(item.count / 2)})</button>
605
+ <button className={styles.mobileBtn} {...touchBtn(onPickCustom)}>Take Amount…</button>
606
+ <button className={[styles.mobileBtn, styles.mobileBtnDanger].join(' ')} {...touchBtn(onDrop)}>Drop</button>
607
+ <button className={styles.mobileBtn} {...touchBtn(onClose)}>Cancel</button>
535
608
  </div>
536
609
  )
537
610
  }