minecraft-inventory 0.1.22 → 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.22",
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.22
313
+ INV 0.1.24
314
314
  </a>
315
315
  )}
316
316
 
@@ -1,5 +1,6 @@
1
- import React from 'react'
1
+ import React, { useState } from 'react'
2
2
  import { useInventoryContext } from '../../context/InventoryContext'
3
+ import { useTextures } from '../../context/TextureContext'
3
4
  import { useScale } from '../../context/ScaleContext'
4
5
 
5
6
  const ENCHANTMENT_NAMES: Record<number, string> = {
@@ -59,6 +60,8 @@ function toRoman(num: number): string {
59
60
  return result
60
61
  }
61
62
 
63
+ const SPRITE_BASE = '1.21.11/textures/gui/sprites/container/enchanting_table'
64
+
62
65
  interface EnchantmentOptionsProps {
63
66
  properties: Record<string, number>
64
67
  x: number
@@ -72,8 +75,27 @@ const OPTION_KEYS = [
72
75
  ]
73
76
 
74
77
  export function EnchantmentOptions({ properties, x, y }: EnchantmentOptionsProps) {
75
- const { sendAction } = useInventoryContext()
78
+ const { sendAction, resolveEnchantmentName } = useInventoryContext()
79
+ const textures = useTextures()
76
80
  const { scale } = useScale()
81
+ const [hoveredSlot, setHoveredSlot] = useState<number | null>(null)
82
+
83
+ const getName = (id: number): string => {
84
+ if (id < 0) return '???'
85
+ return resolveEnchantmentName?.(id) ?? ENCHANTMENT_NAMES[id] ?? `Enchant #${id}`
86
+ }
87
+
88
+ const getSlotSprite = (disabled: boolean, hovered: boolean) => {
89
+ if (disabled) return textures.getGuiTextureUrl(`${SPRITE_BASE}/enchantment_slot_disabled.png`)
90
+ if (hovered) return textures.getGuiTextureUrl(`${SPRITE_BASE}/enchantment_slot_highlighted.png`)
91
+ return textures.getGuiTextureUrl(`${SPRITE_BASE}/enchantment_slot.png`)
92
+ }
93
+
94
+ const getLevelSprite = (slot: number, disabled: boolean) => {
95
+ const n = slot + 1
96
+ if (disabled) return textures.getGuiTextureUrl(`${SPRITE_BASE}/level_${n}_disabled.png`)
97
+ return textures.getGuiTextureUrl(`${SPRITE_BASE}/level_${n}.png`)
98
+ }
77
99
 
78
100
  return (
79
101
  <div
@@ -90,36 +112,100 @@ export function EnchantmentOptions({ properties, x, y }: EnchantmentOptionsProps
90
112
  const level = properties[levelKey] ?? 0
91
113
  const enchId = properties[idKey] ?? -1
92
114
  const levelClue = properties[clueKey] ?? 0
93
- const name = ENCHANTMENT_NAMES[enchId] ?? (enchId >= 0 ? `Enchant #${enchId}` : '???')
94
115
  const disabled = level <= 0
116
+ const hovered = hoveredSlot === i && !disabled
117
+ const name = getName(enchId)
95
118
  const levelSuffix = levelClue > 0 ? ` ${toRoman(levelClue)}` : ''
119
+ // Vanilla colors
120
+ const nameColor = disabled ? '#342F25' : hovered ? '#FFFF80' : '#685E4A'
121
+ const costColor = disabled ? '#407F10' : '#80FF20'
96
122
 
97
123
  return (
98
124
  <div
99
125
  key={i}
100
126
  onClick={() => !disabled && sendAction({ type: 'enchant', enchantIndex: slot })}
127
+ onMouseEnter={() => setHoveredSlot(i)}
128
+ onMouseLeave={() => setHoveredSlot(null)}
101
129
  style={{
130
+ position: 'relative',
102
131
  width: 108 * scale,
103
132
  height: 19 * scale,
104
- display: 'flex',
105
- alignItems: 'center',
106
- padding: `0 ${4 * scale}px`,
107
- gap: 4 * scale,
108
- background: disabled ? 'rgba(0,0,0,0.3)' : 'rgba(0,60,0,0.5)',
109
- cursor: disabled ? 'not-allowed' : 'pointer',
110
- border: `${scale}px solid ${disabled ? '#333333' : '#446644'}`,
111
- boxSizing: 'border-box',
112
- fontSize: 6 * scale,
113
- fontFamily: "'Minecraft', monospace",
114
- color: disabled ? '#555555' : '#80c060',
133
+ cursor: disabled ? 'default' : 'pointer',
134
+ overflow: 'hidden',
115
135
  }}
116
136
  >
117
- <span style={{ color: disabled ? '#555' : '#558855', fontWeight: 'bold' }}>
118
- {level}
119
- </span>
120
- <span style={{ flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
121
- {disabled ? '' : `${name}${levelSuffix}`}
122
- </span>
137
+ {/* Background sprite */}
138
+ <img
139
+ src={getSlotSprite(disabled, hovered)}
140
+ alt=""
141
+ aria-hidden
142
+ draggable={false}
143
+ style={{
144
+ position: 'absolute',
145
+ left: 0,
146
+ top: 0,
147
+ width: 108 * scale,
148
+ height: 19 * scale,
149
+ imageRendering: 'pixelated',
150
+ }}
151
+ />
152
+ {/* Level icon (16×16 at +1,+1) */}
153
+ {!disabled && (
154
+ <img
155
+ src={getLevelSprite(i, disabled)}
156
+ alt=""
157
+ aria-hidden
158
+ draggable={false}
159
+ style={{
160
+ position: 'absolute',
161
+ left: 1 * scale,
162
+ top: 1 * scale,
163
+ width: 16 * scale,
164
+ height: 16 * scale,
165
+ imageRendering: 'pixelated',
166
+ }}
167
+ />
168
+ )}
169
+ {/* Enchantment name text (at +20, +2) */}
170
+ {!disabled && (
171
+ <span
172
+ style={{
173
+ position: 'absolute',
174
+ left: 20 * scale,
175
+ top: 2 * scale,
176
+ fontSize: 8 * scale,
177
+ fontFamily: "'Minecraft', monospace",
178
+ color: nameColor,
179
+ whiteSpace: 'nowrap',
180
+ overflow: 'hidden',
181
+ textOverflow: 'clip',
182
+ maxWidth: 70 * scale,
183
+ lineHeight: `${16 * scale}px`,
184
+ textShadow: 'none',
185
+ }}
186
+ >
187
+ {`${name}${levelSuffix}`}
188
+ </span>
189
+ )}
190
+ {/* Cost number (right-aligned, vertically centered) */}
191
+ {!disabled && (
192
+ <span
193
+ style={{
194
+ position: 'absolute',
195
+ right: 2 * scale,
196
+ top: 0,
197
+ height: 19 * scale,
198
+ display: 'flex',
199
+ alignItems: 'center',
200
+ fontSize: 8 * scale,
201
+ fontFamily: "'Minecraft', monospace",
202
+ color: costColor,
203
+ textShadow: 'none',
204
+ }}
205
+ >
206
+ {level}
207
+ </span>
208
+ )}
123
209
  </div>
124
210
  )
125
211
  })}
@@ -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
  }
@@ -49,6 +49,8 @@ export interface InventoryContextValue {
49
49
  dragEndedRef: React.MutableRefObject<boolean>
50
50
  /** When true, empty slot labels (Head, Chest, Legs, etc.) are not rendered */
51
51
  noPlaceholders: boolean
52
+ /** Optional resolver for enchantment ID → display name (version-aware) */
53
+ resolveEnchantmentName?: (id: number) => string | undefined
52
54
  }
53
55
 
54
56
  const InventoryContext = createContext<InventoryContextValue | null>(null)
@@ -64,9 +66,10 @@ interface InventoryProviderProps {
64
66
  children: React.ReactNode
65
67
  noDragSpread?: boolean
66
68
  noPlaceholders?: boolean
69
+ resolveEnchantmentName?: (id: number) => string | undefined
67
70
  }
68
71
 
69
- export function InventoryProvider({ connector, children, noDragSpread = false, noPlaceholders = false }: InventoryProviderProps) {
72
+ export function InventoryProvider({ connector, children, noDragSpread = false, noPlaceholders = false, resolveEnchantmentName }: InventoryProviderProps) {
70
73
  const [windowState, setWindowState] = useState<InventoryWindowState | null>(
71
74
  () => connector?.getWindowState() ?? null,
72
75
  )
@@ -318,6 +321,7 @@ export function InventoryProvider({ connector, children, noDragSpread = false, n
318
321
  pKeyDigit,
319
322
  setPKeyDigit,
320
323
  dragEndedRef,
324
+ resolveEnchantmentName,
321
325
  }
322
326
 
323
327
  valueRef.current = value
@@ -35,6 +35,15 @@ import _gui_sprites_container_smoker_lit_progress from 'mc-assets/dist/other-tex
35
35
  import _gui_sprites_container_smoker_burn_progress from 'mc-assets/dist/other-textures/latest/gui/sprites/container/smoker/burn_progress.png'
36
36
  import _gui_sprites_container_brewing_stand_brew_progress from 'mc-assets/dist/other-textures/latest/gui/sprites/container/brewing_stand/brew_progress.png'
37
37
  import _gui_sprites_container_brewing_stand_fuel_length from 'mc-assets/dist/other-textures/latest/gui/sprites/container/brewing_stand/fuel_length.png'
38
+ import _gui_sprites_container_enchanting_table_enchantment_slot from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/enchantment_slot.png'
39
+ import _gui_sprites_container_enchanting_table_enchantment_slot_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/enchantment_slot_disabled.png'
40
+ import _gui_sprites_container_enchanting_table_enchantment_slot_highlighted from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png'
41
+ import _gui_sprites_container_enchanting_table_level_1 from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_1.png'
42
+ import _gui_sprites_container_enchanting_table_level_2 from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_2.png'
43
+ import _gui_sprites_container_enchanting_table_level_3 from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_3.png'
44
+ import _gui_sprites_container_enchanting_table_level_1_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_1_disabled.png'
45
+ import _gui_sprites_container_enchanting_table_level_2_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_2_disabled.png'
46
+ import _gui_sprites_container_enchanting_table_level_3_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/enchanting_table/level_3_disabled.png'
38
47
 
39
48
  /**
40
49
  * Versioned texture path → bundled asset URL (or undefined for remote fallback).
@@ -75,6 +84,15 @@ export const bundledTextureMap: Record<string, string | undefined> = {
75
84
  '1.21.11/textures/gui/sprites/container/smoker/burn_progress.png': _gui_sprites_container_smoker_burn_progress,
76
85
  '1.21.11/textures/gui/sprites/container/brewing_stand/brew_progress.png': _gui_sprites_container_brewing_stand_brew_progress,
77
86
  '1.21.11/textures/gui/sprites/container/brewing_stand/fuel_length.png': _gui_sprites_container_brewing_stand_fuel_length,
87
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot.png': _gui_sprites_container_enchanting_table_enchantment_slot,
88
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_disabled.png': _gui_sprites_container_enchanting_table_enchantment_slot_disabled,
89
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png': _gui_sprites_container_enchanting_table_enchantment_slot_highlighted,
90
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1.png': _gui_sprites_container_enchanting_table_level_1,
91
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2.png': _gui_sprites_container_enchanting_table_level_2,
92
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3.png': _gui_sprites_container_enchanting_table_level_3,
93
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1_disabled.png': _gui_sprites_container_enchanting_table_level_1_disabled,
94
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2_disabled.png': _gui_sprites_container_enchanting_table_level_2_disabled,
95
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3_disabled.png': _gui_sprites_container_enchanting_table_level_3_disabled,
78
96
  }
79
97
 
80
98
 
@@ -116,6 +134,15 @@ export const allTexturePaths: readonly string[] = [
116
134
  'gui/sprites/container/smoker/burn_progress.png',
117
135
  'gui/sprites/container/brewing_stand/brew_progress.png',
118
136
  'gui/sprites/container/brewing_stand/fuel_length.png',
137
+ 'gui/sprites/container/enchanting_table/enchantment_slot.png',
138
+ 'gui/sprites/container/enchanting_table/enchantment_slot_disabled.png',
139
+ 'gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png',
140
+ 'gui/sprites/container/enchanting_table/level_1.png',
141
+ 'gui/sprites/container/enchanting_table/level_2.png',
142
+ 'gui/sprites/container/enchanting_table/level_3.png',
143
+ 'gui/sprites/container/enchanting_table/level_1_disabled.png',
144
+ 'gui/sprites/container/enchanting_table/level_2_disabled.png',
145
+ 'gui/sprites/container/enchanting_table/level_3_disabled.png',
119
146
  ]
120
147
 
121
148
  /**
@@ -749,4 +749,14 @@ export const registryExtraTextures: string[] = [
749
749
  // Brewing stand progress bar sprites
750
750
  '1.21.11/textures/gui/sprites/container/brewing_stand/brew_progress.png',
751
751
  '1.21.11/textures/gui/sprites/container/brewing_stand/fuel_length.png',
752
+ // Enchanting table slot backgrounds & level icons
753
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot.png',
754
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_disabled.png',
755
+ '1.21.11/textures/gui/sprites/container/enchanting_table/enchantment_slot_highlighted.png',
756
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1.png',
757
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2.png',
758
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3.png',
759
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_1_disabled.png',
760
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_2_disabled.png',
761
+ '1.21.11/textures/gui/sprites/container/enchanting_table/level_3_disabled.png',
752
762
  ]