minecraft-inventory 0.1.3 → 0.1.5

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.
@@ -1,6 +1,11 @@
1
- import React, { createContext, useContext, useEffect, useRef, useState, useCallback } from 'react'
1
+ import React, { createContext, useContext, useEffect, useRef, useState, useCallback, useMemo } from 'react'
2
2
  import type { InventoryWindowState, PlayerState, ItemStack, SlotState } from '../types'
3
3
  import type { InventoryConnector } from '../connector/types'
4
+ import { isItemEqual, getMaxStackSize } from '../utils/isItemEqual'
5
+
6
+ export interface DragPreviewEntry {
7
+ count: number
8
+ }
4
9
 
5
10
  export interface InventoryContextValue {
6
11
  windowState: InventoryWindowState | null
@@ -12,6 +17,8 @@ export interface InventoryContextValue {
12
17
  isDragging: boolean
13
18
  dragSlots: number[]
14
19
  dragButton: 'left' | 'right' | null
20
+ /** Client-side preview of item counts for each slot in the current drag */
21
+ dragPreview: Map<number, DragPreviewEntry>
15
22
  startDrag: (slotIndex: number, button: 'left' | 'right') => void
16
23
  addDragSlot: (slotIndex: number) => void
17
24
  endDrag: () => void
@@ -24,6 +31,17 @@ export interface InventoryContextValue {
24
31
  /** Pixel offset from the cursor item's top-left to the grab point, preserving pick-up position */
25
32
  grabOffset: { x: number; y: number }
26
33
  setGrabOffset: (offset: { x: number; y: number }) => void
34
+ /** Whether drag/spread operations are disabled */
35
+ noDragSpread: boolean
36
+ /** Whether P-key slot numbering mode is active */
37
+ pKeyActive: boolean
38
+ setPKeyActive: (v: boolean) => void
39
+ /** Currently focused slot index (via P-key number entry) */
40
+ focusedSlot: number | null
41
+ setFocusedSlot: (slot: number | null) => void
42
+ /** Pending first digit for P-key slot number entry */
43
+ pKeyDigit: string
44
+ setPKeyDigit: (d: string) => void
27
45
  }
28
46
 
29
47
  const InventoryContext = createContext<InventoryContextValue | null>(null)
@@ -37,9 +55,10 @@ export function useInventoryContext(): InventoryContextValue {
37
55
  interface InventoryProviderProps {
38
56
  connector: InventoryConnector | null
39
57
  children: React.ReactNode
58
+ noDragSpread?: boolean
40
59
  }
41
60
 
42
- export function InventoryProvider({ connector, children }: InventoryProviderProps) {
61
+ export function InventoryProvider({ connector, children, noDragSpread = false }: InventoryProviderProps) {
43
62
  const [windowState, setWindowState] = useState<InventoryWindowState | null>(
44
63
  () => connector?.getWindowState() ?? null,
45
64
  )
@@ -53,12 +72,24 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
53
72
  const [isDragging, setIsDragging] = useState(false)
54
73
  const [dragSlots, setDragSlots] = useState<number[]>([])
55
74
  const [dragButton, setDragButton] = useState<'left' | 'right' | null>(null)
75
+ const [dragPreview, setDragPreview] = useState<Map<number, DragPreviewEntry>>(new Map())
56
76
  const [activeNumberKey, setActiveNumberKey] = useState<number | null>(null)
57
77
  const [grabOffset, setGrabOffset] = useState<{ x: number; y: number }>({ x: 0, y: 0 })
78
+ const [pKeyActive, setPKeyActive] = useState(false)
79
+ const [focusedSlot, setFocusedSlot] = useState<number | null>(null)
80
+ const [pKeyDigit, setPKeyDigit] = useState('')
58
81
 
59
82
  const connectorRef = useRef(connector)
60
83
  connectorRef.current = connector
61
84
 
85
+ // Refs so endDrag can read current values without being in its dep array
86
+ const heldItemRef = useRef(heldItem)
87
+ heldItemRef.current = heldItem
88
+ const windowStateRef = useRef(windowState)
89
+ windowStateRef.current = windowState
90
+ const dragButtonRef = useRef(dragButton)
91
+ dragButtonRef.current = dragButton
92
+
62
93
  useEffect(() => {
63
94
  if (!connector) return
64
95
  setWindowState(connector.getWindowState())
@@ -92,35 +123,134 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
92
123
  [],
93
124
  )
94
125
 
126
+ const computeDragPreview = useCallback((slots: number[], button: 'left' | 'right', held: ItemStack | null, ws: InventoryWindowState | null) => {
127
+ const preview = new Map<number, DragPreviewEntry>()
128
+ if (!held || slots.length === 0) return preview
129
+
130
+ const maxStack = getMaxStackSize(held)
131
+
132
+ if (button === 'left') {
133
+ // Only spread into compatible slots (empty or same item type)
134
+ const compatibleSlots = slots.filter((idx) => {
135
+ const existingItem = ws?.slots.find((s) => s.index === idx)?.item
136
+ return !existingItem || isItemEqual(existingItem, held)
137
+ })
138
+ if (compatibleSlots.length === 0) return preview
139
+ const perSlot = Math.floor(held.count / compatibleSlots.length)
140
+ // Vanilla behavior: if perSlot=0 (more slots than items), nothing is distributed
141
+ if (perSlot === 0) return preview
142
+ for (const idx of compatibleSlots) {
143
+ const existingCount = ws?.slots.find((s) => s.index === idx)?.item?.count ?? 0
144
+ const total = Math.min(existingCount + perSlot, maxStack)
145
+ preview.set(idx, { count: total })
146
+ }
147
+ } else {
148
+ for (const idx of slots) {
149
+ const existing = ws?.slots.find((s) => s.index === idx)
150
+ const existingItem = existing?.item
151
+ if (existingItem && !isItemEqual(existingItem, held)) continue
152
+ const existingCount = existingItem ? existingItem.count : 0
153
+ const total = Math.min(existingCount + 1, maxStack)
154
+ preview.set(idx, { count: total })
155
+ }
156
+ }
157
+ return preview
158
+ }, [])
159
+
95
160
  const startDrag = useCallback((slotIndex: number, button: 'left' | 'right') => {
161
+ if (noDragSpread) return
96
162
  setIsDragging(true)
97
163
  setDragButton(button)
98
164
  setDragSlots([slotIndex])
99
- connectorRef.current?.sendAction({ type: 'drag', slots: [], button })
100
- }, [])
165
+ setDragPreview(new Map())
166
+ }, [noDragSpread])
101
167
 
102
168
  const addDragSlot = useCallback((slotIndex: number) => {
103
169
  setDragSlots((prev) => {
104
170
  if (prev.includes(slotIndex)) return prev
105
- return [...prev, slotIndex]
171
+ const next = [...prev, slotIndex]
172
+ setDragPreview(computeDragPreview(next, dragButton!, heldItem, windowState))
173
+ return next
106
174
  })
107
- }, [])
175
+ }, [computeDragPreview, dragButton, heldItem, windowState])
108
176
 
109
177
  const endDrag = useCallback(() => {
110
178
  setDragSlots((slots) => {
111
- if (slots.length > 0 && dragButton) {
112
- connectorRef.current?.sendAction({ type: 'drag', slots, button: dragButton })
179
+ const button = dragButtonRef.current
180
+ const held = heldItemRef.current
181
+ const ws = windowStateRef.current
182
+
183
+ // Only send drag action if multiple slots were involved (single slot = normal click)
184
+ if (slots.length > 1 && button && held) {
185
+ connectorRef.current?.sendAction({ type: 'drag', slots, button })
186
+
187
+ // Optimistic client-side update: apply item distribution immediately
188
+ // so slots visually update before server responds.
189
+ const maxStack = getMaxStackSize(held)
190
+ const newSlots = ws ? [...ws.slots] : []
191
+
192
+ if (button === 'left') {
193
+ const compatibleSlots = slots.filter((idx) => {
194
+ const existing = newSlots.find((s) => s.index === idx)?.item
195
+ return !existing || isItemEqual(existing, held)
196
+ })
197
+ if (compatibleSlots.length > 0) {
198
+ const perSlot = Math.floor(held.count / compatibleSlots.length)
199
+ // Vanilla behavior: if perSlot=0 (more slots than items), nothing is distributed
200
+ if (perSlot > 0) {
201
+ let totalPlaced = 0
202
+ for (const idx of compatibleSlots) {
203
+ const existingIdx = newSlots.findIndex((s) => s.index === idx)
204
+ const existingCount = existingIdx >= 0 ? (newSlots[existingIdx].item?.count ?? 0) : 0
205
+ const add = Math.min(perSlot, maxStack - existingCount)
206
+ totalPlaced += add
207
+ const newCount = existingCount + add
208
+ if (existingIdx >= 0) {
209
+ newSlots[existingIdx] = { index: idx, item: { ...held, count: newCount } }
210
+ } else {
211
+ newSlots.push({ index: idx, item: { ...held, count: newCount } })
212
+ }
213
+ }
214
+ if (ws) setWindowState({ ...ws, slots: newSlots })
215
+ const remaining = held.count - totalPlaced
216
+ if (remaining > 0) setHeldItemState({ ...held, count: remaining })
217
+ else setHeldItemState(null)
218
+ }
219
+ }
220
+ } else {
221
+ // Right-click drag: place 1 per slot
222
+ let remaining = held.count
223
+ for (const idx of slots) {
224
+ if (remaining <= 0) break
225
+ const existingIdx = newSlots.findIndex((s) => s.index === idx)
226
+ const existingItem = existingIdx >= 0 ? newSlots[existingIdx].item : null
227
+ if (existingItem && !isItemEqual(existingItem, held)) continue
228
+ const existingCount = existingItem?.count ?? 0
229
+ const newCount = Math.min(existingCount + 1, maxStack)
230
+ if (existingIdx >= 0) {
231
+ newSlots[existingIdx] = { index: idx, item: { ...held, count: newCount } }
232
+ } else {
233
+ newSlots.push({ index: idx, item: { ...held, count: newCount } })
234
+ }
235
+ remaining--
236
+ }
237
+ if (ws) setWindowState({ ...ws, slots: newSlots })
238
+ if (remaining <= 0) setHeldItemState(null)
239
+ else setHeldItemState({ ...held, count: remaining })
240
+ }
113
241
  }
114
242
  return []
115
243
  })
116
244
  setIsDragging(false)
117
245
  setDragButton(null)
118
- }, [dragButton])
246
+ setDragPreview(new Map())
247
+ }, [])
119
248
 
120
249
  const cancelDrag = useCallback(() => {
121
250
  setIsDragging(false)
122
251
  setDragSlots([])
123
252
  setDragButton(null)
253
+ setDragPreview(new Map())
124
254
  }, [])
125
255
 
126
256
  const getSlot = useCallback(
@@ -130,6 +260,19 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
130
260
  [windowState],
131
261
  )
132
262
 
263
+ // Expose state to window for easier debugging from browser DevTools.
264
+ // Access via: window.__mcInv
265
+ useEffect(() => {
266
+ ;(window as unknown as Record<string, unknown>).__mcInv = {
267
+ get windowState() { return windowStateRef.current },
268
+ get heldItem() { return heldItemRef.current },
269
+ get dragSlots() { return dragSlots },
270
+ get isDragging() { return isDragging },
271
+ get focusedSlot() { return focusedSlot },
272
+ get pKeyActive() { return pKeyActive },
273
+ }
274
+ })
275
+
133
276
  const value: InventoryContextValue = {
134
277
  windowState,
135
278
  playerState,
@@ -140,6 +283,7 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
140
283
  isDragging,
141
284
  dragSlots,
142
285
  dragButton,
286
+ dragPreview,
143
287
  startDrag,
144
288
  addDragSlot,
145
289
  endDrag,
@@ -151,6 +295,13 @@ export function InventoryProvider({ connector, children }: InventoryProviderProp
151
295
  getSlot,
152
296
  grabOffset,
153
297
  setGrabOffset,
298
+ noDragSpread,
299
+ pKeyActive,
300
+ setPKeyActive,
301
+ focusedSlot,
302
+ setFocusedSlot,
303
+ pKeyDigit,
304
+ setPKeyDigit,
154
305
  }
155
306
 
156
307
  return <InventoryContext.Provider value={value}>{children}</InventoryContext.Provider>
@@ -9,7 +9,13 @@ export const MC_GUI_BASE = MC_ASSETS_BASE
9
9
 
10
10
  export interface TextureConfig {
11
11
  baseUrl: string
12
- getItemTextureUrl(item: { type: number; name?: string }): string
12
+ /**
13
+ * Resolve item texture URL.
14
+ * When `item.textureKey` is set it is used as the path relative to the items texture root
15
+ * (e.g. `"item/dye_black"` → `<base>/item/dye_black.png`).
16
+ * Otherwise falls back to `name` then `type`.
17
+ */
18
+ getItemTextureUrl(item: { type: number; name?: string; textureKey?: string }): string
13
19
  getBlockTextureUrl(item: { type: number; name?: string }): string
14
20
  /** Supports full mc-assets paths (e.g. "1.21.11/textures/gui/container/anvil.png") */
15
21
  getGuiTextureUrl(path: string, version?: string): string
@@ -23,8 +29,9 @@ function buildDefault(base: string): TextureConfig {
23
29
  return {
24
30
  baseUrl: base,
25
31
 
26
- getItemTextureUrl({ type, name }) {
32
+ getItemTextureUrl({ type, name, textureKey }) {
27
33
  const root = isRemote ? MC_ITEMS_BASE : base
34
+ if (textureKey) return `${root}/${textureKey}.png`
28
35
  if (name) return `${root}/item/${name}.png`
29
36
  return `${root}/item/${type}.png`
30
37
  },
@@ -1,5 +1,5 @@
1
1
  // Auto-generated by scripts/generate-texture-imports.mjs — do not edit manually
2
- // Re-run `node scripts/generate-texture-imports.mjs` after changing inventories.
2
+ // Re-run `pnpm gen:textures` after changing inventories.
3
3
 
4
4
  import _gui_container_inventory from 'mc-assets/dist/other-textures/latest/gui/container/inventory.png'
5
5
  import _gui_container_shulker_box from 'mc-assets/dist/other-textures/latest/gui/container/shulker_box.png'
@@ -13,6 +13,7 @@ import _gui_container_anvil from 'mc-assets/dist/other-textures/latest/gui/conta
13
13
  import _gui_container_grindstone from 'mc-assets/dist/other-textures/latest/gui/container/grindstone.png'
14
14
  import _gui_container_enchanting_table from 'mc-assets/dist/other-textures/latest/gui/container/enchanting_table.png'
15
15
  import _gui_container_smithing from 'mc-assets/dist/other-textures/latest/gui/container/smithing.png'
16
+ import _gui_container_smithing_2 from 'mc-assets/dist/other-textures/1.19.4/gui/container/smithing.png'
16
17
  import _gui_container_hopper from 'mc-assets/dist/other-textures/latest/gui/container/hopper.png'
17
18
  import _gui_container_dispenser from 'mc-assets/dist/other-textures/latest/gui/container/dispenser.png'
18
19
  import _gui_container_beacon from 'mc-assets/dist/other-textures/latest/gui/container/beacon.png'
@@ -24,15 +25,21 @@ import _gui_container_stonecutter from 'mc-assets/dist/other-textures/latest/gui
24
25
  import _gui_container_crafter from 'mc-assets/dist/other-textures/latest/gui/container/crafter.png'
25
26
  import _gui_container_creative_inventory_tab_items from 'mc-assets/dist/other-textures/latest/gui/container/creative_inventory/tab_items.png'
26
27
  import _gui_widgets from 'mc-assets/dist/other-textures/1.15/gui/widgets.png'
28
+ import _gui_sprites_container_anvil_text_field from 'mc-assets/dist/other-textures/latest/gui/sprites/container/anvil/text_field.png'
29
+ import _gui_sprites_container_anvil_text_field_disabled from 'mc-assets/dist/other-textures/latest/gui/sprites/container/anvil/text_field_disabled.png'
27
30
 
28
31
  /**
29
32
  * Maps each inventory type name to its texture path (version prefix stripped).
30
- * Useful for building custom texture resolvers or inspecting the full texture list.
31
33
  */
32
34
  export const allContainerPaths: Record<string, string> = {
33
35
  player: 'gui/container/inventory.png',
34
36
  chest: 'gui/container/shulker_box.png',
35
- generic_9x1: 'gui/container/shulker_box.png',
37
+ generic_9x1: 'gui/container/generic_54.png',
38
+ generic_9x2: 'gui/container/generic_54.png',
39
+ generic_9x3: 'gui/container/generic_54.png',
40
+ generic_9x4: 'gui/container/generic_54.png',
41
+ generic_9x5: 'gui/container/generic_54.png',
42
+ generic_9x6: 'gui/container/generic_54.png',
36
43
  large_chest: 'gui/container/generic_54.png',
37
44
  crafting_table: 'gui/container/crafting_table.png',
38
45
  furnace: 'gui/container/furnace.png',
@@ -62,7 +69,7 @@ export const allContainerPaths: Record<string, string> = {
62
69
  hotbar: 'gui/widgets.png',
63
70
  }
64
71
 
65
- // Internal: full versioned texture path → bundled asset URL (or undefined = not found)
72
+ // Internal: versioned texture key → bundled asset URL (or undefined remote fallback)
66
73
  const _map: Record<string, string | undefined> = {
67
74
  '1.21.11/textures/gui/container/inventory.png': _gui_container_inventory,
68
75
  '1.21.11/textures/gui/container/shulker_box.png': _gui_container_shulker_box,
@@ -76,7 +83,7 @@ const _map: Record<string, string | undefined> = {
76
83
  '1.21.11/textures/gui/container/grindstone.png': _gui_container_grindstone,
77
84
  '1.21.11/textures/gui/container/enchanting_table.png': _gui_container_enchanting_table,
78
85
  '1.21.11/textures/gui/container/smithing.png': _gui_container_smithing,
79
- '1.16.4/textures/gui/container/smithing.png': _gui_container_smithing,
86
+ '1.16.4/textures/gui/container/smithing.png': _gui_container_smithing_2,
80
87
  '1.21.11/textures/gui/container/hopper.png': _gui_container_hopper,
81
88
  '1.21.11/textures/gui/container/dispenser.png': _gui_container_dispenser,
82
89
  '1.21.11/textures/gui/container/beacon.png': _gui_container_beacon,
@@ -88,25 +95,27 @@ const _map: Record<string, string | undefined> = {
88
95
  '1.21.11/textures/gui/container/crafter.png': _gui_container_crafter,
89
96
  '1.21.11/textures/gui/container/creative_inventory/tab_items.png': _gui_container_creative_inventory_tab_items,
90
97
  '1.15/textures/gui/widgets.png': _gui_widgets,
98
+ '1.21.11/textures/gui/sprites/container/anvil/text_field.png': _gui_sprites_container_anvil_text_field,
99
+ '1.21.11/textures/gui/sprites/container/anvil/text_field_disabled.png': _gui_sprites_container_anvil_text_field_disabled,
91
100
  }
92
101
 
93
102
  /**
94
103
  * Partial TextureConfig that resolves inventory GUI textures from locally bundled
95
104
  * mc-assets assets instead of remote GitHub URLs.
96
105
  *
97
- * Pass to `<TextureProvider config={localTexturesConfig}>` to use offline/bundled assets:
106
+ * Pass to `<TextureProvider config={localTexturesConfig}>` to use offline/bundled assets.
98
107
  *
99
- * ```tsx
100
- * import { localTexturesConfig } from './generated/localTextures'
101
- * <TextureProvider config={localTexturesConfig}>
102
- * <InventoryOverlay type="chest" ... />
103
- * </TextureProvider>
104
- * ```
108
+ * Unknown paths (not bundled) fall back to the mc-assets remote URL automatically.
105
109
  */
110
+ const _MC_ASSETS_REMOTE = "https://raw.githubusercontent.com/zardoy/mc-assets/refs/heads/gh-pages"
106
111
  export const localTexturesConfig = {
107
112
  getGuiTextureUrl(path: string): string {
108
- // _map keys are the full versioned paths used in inventories.ts
109
- // e.g. '1.21.11/textures/gui/container/anvil.png'
110
- return (_map[path] as string | undefined) ?? path
113
+ const local = _map[path] as string | undefined
114
+ if (local) return local
115
+ // Fall back to remote mc-assets URL for paths not in the bundle
116
+ if (path.endsWith('.png') && path.includes('/textures/')) {
117
+ return `${_MC_ASSETS_REMOTE}/${path}`
118
+ }
119
+ return path
111
120
  },
112
121
  }
@@ -1,15 +1,70 @@
1
- import { useEffect } from 'react'
1
+ import { useEffect, useCallback, useRef } from 'react'
2
2
  import { useInventoryContext } from '../context/InventoryContext'
3
3
 
4
4
  export function useKeyboardShortcuts(enabled = true) {
5
- const { setActiveNumberKey, hoveredSlot, sendAction } = useInventoryContext()
5
+ const {
6
+ setActiveNumberKey,
7
+ hoveredSlot,
8
+ sendAction,
9
+ pKeyActive,
10
+ setPKeyActive,
11
+ focusedSlot,
12
+ setFocusedSlot,
13
+ pKeyDigit,
14
+ setPKeyDigit,
15
+ } = useInventoryContext()
16
+
17
+ const pKeyDigitRef = useRef(pKeyDigit)
18
+ pKeyDigitRef.current = pKeyDigit
6
19
 
7
20
  useEffect(() => {
8
21
  if (!enabled) return
9
22
 
10
23
  const handleKeyDown = (e: KeyboardEvent) => {
11
- // Digit1–Digit9 hotbar swap (works regardless of keyboard layout)
12
- if (e.code >= 'Digit1' && e.code <= 'Digit9') {
24
+ // Ignore when typing in inputs
25
+ if ((e.target as HTMLElement)?.tagName === 'INPUT' || (e.target as HTMLElement)?.tagName === 'TEXTAREA') return
26
+
27
+ // P toggles slot numbering mode
28
+ if (e.code === 'KeyP' && !e.shiftKey && !e.ctrlKey && !e.metaKey) {
29
+ if (pKeyActive) {
30
+ setPKeyActive(false)
31
+ setFocusedSlot(null)
32
+ setPKeyDigit('')
33
+ } else {
34
+ setPKeyActive(true)
35
+ setPKeyDigit('')
36
+ }
37
+ return
38
+ }
39
+
40
+ // Escape exits P-key mode
41
+ if (e.code === 'Escape' && pKeyActive) {
42
+ setPKeyActive(false)
43
+ setFocusedSlot(null)
44
+ setPKeyDigit('')
45
+ return
46
+ }
47
+
48
+ // Number entry during P-key mode
49
+ if (pKeyActive && e.code >= 'Digit0' && e.code <= 'Digit9') {
50
+ const digit = e.code.replace('Digit', '')
51
+ const current = pKeyDigitRef.current
52
+ if (current.length === 0) {
53
+ setPKeyDigit(digit)
54
+ } else {
55
+ const slotNum = parseInt(current + digit, 10)
56
+ setPKeyDigit('')
57
+ if (slotNum <= 99) {
58
+ setFocusedSlot(slotNum)
59
+ // Exit P mode but keep focused slot
60
+ setPKeyActive(false)
61
+ }
62
+ }
63
+ return
64
+ }
65
+
66
+ // Normal hotbar number keys (only when P-key not active)
67
+ if (!pKeyActive && e.code >= 'Digit1' && e.code <= 'Digit9') {
13
68
  setActiveNumberKey(parseInt(e.code.replace('Digit', ''), 10) - 1)
14
69
  }
15
70
 
@@ -26,7 +81,7 @@ export function useKeyboardShortcuts(enabled = true) {
26
81
  }
27
82
 
28
83
  const handleKeyUp = (e: KeyboardEvent) => {
29
- if (e.code >= 'Digit1' && e.code <= 'Digit9') {
84
+ if (!pKeyActive && e.code >= 'Digit1' && e.code <= 'Digit9') {
30
85
  setActiveNumberKey(null)
31
86
  }
32
87
  }
@@ -37,5 +92,5 @@ export function useKeyboardShortcuts(enabled = true) {
37
92
  window.removeEventListener('keydown', handleKeyDown)
38
93
  window.removeEventListener('keyup', handleKeyUp)
39
94
  }
40
- }, [enabled, hoveredSlot, sendAction, setActiveNumberKey])
95
+ }, [enabled, hoveredSlot, sendAction, setActiveNumberKey, pKeyActive, setPKeyActive, focusedSlot, setFocusedSlot, setPKeyDigit])
41
96
  }
package/src/index.tsx CHANGED
@@ -34,6 +34,9 @@ export type { MountedInventory } from './mount'
34
34
  export { useMobile } from './hooks/useMobile'
35
35
  export { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'
36
36
 
37
+ // Utilities
38
+ export { isItemEqual, getMaxStackSize } from './utils/isItemEqual'
39
+
37
40
  // Connector
38
41
  export { createMineflayerConnector } from './connector/mineflayer'
39
42
  export { createDemoConnector } from './connector/demo'
@@ -46,7 +49,7 @@ export type {
46
49
  export type { ActionLogEntry, DemoConnectorOptions } from './connector/demo'
47
50
 
48
51
  // Registry
49
- export { registerInventoryType, getInventoryType, getAllInventoryTypes } from './registry'
52
+ export { registerInventoryType, registerTypeAlias, getInventoryType, getAllInventoryTypes } from './registry'
50
53
  export type { InventoryTypeDefinition, WindowType } from './registry'
51
54
 
52
55
  // Types
@@ -63,4 +66,5 @@ export type {
63
66
  ClickMode,
64
67
  RecipeGuide,
65
68
  RecipeNavFrame,
69
+ EntityDisplayArea,
66
70
  } from './types'
@@ -5,12 +5,41 @@ const registry = new Map<string, InventoryTypeDefinition>(
5
5
  Object.entries(inventoryDefinitions),
6
6
  )
7
7
 
8
+ /**
9
+ * Maps alternative names/aliases to canonical inventory type names.
10
+ * Both the alias and the canonical name resolve to the same definition.
11
+ *
12
+ * Add entries here to support shorthand or legacy type identifiers.
13
+ */
14
+ const typeAliases: Record<string, string> = {
15
+ // Shorthand aliases
16
+ crafting3x3: 'crafting_table',
17
+ crafting: 'crafting_table',
18
+ chest: 'chest', // already canonical, listed for clarity
19
+ // Protocol-level minecraft: prefix stripping is handled in getInventoryType
20
+ }
21
+
8
22
  export function registerInventoryType(def: InventoryTypeDefinition): void {
9
23
  registry.set(def.name, def)
10
24
  }
11
25
 
26
+ /**
27
+ * Resolve an inventory type name or alias to its definition.
28
+ * Handles:
29
+ * - Exact matches (e.g. "crafting_table")
30
+ * - Aliases defined in `typeAliases` (e.g. "crafting3x3" → "crafting_table")
31
+ * - "minecraft:" namespace prefix (e.g. "minecraft:generic_9x3" → "generic_9x3")
32
+ */
12
33
  export function getInventoryType(name: string): InventoryTypeDefinition | undefined {
13
- return registry.get(name)
34
+ // Strip "minecraft:" namespace prefix if present
35
+ const stripped = name.startsWith('minecraft:') ? name.slice('minecraft:'.length) : name
36
+ // Resolve alias if defined
37
+ const canonical = typeAliases[stripped] ?? stripped
38
+ return registry.get(canonical)
39
+ }
40
+
41
+ export function registerTypeAlias(alias: string, canonical: string): void {
42
+ typeAliases[alias] = canonical
14
43
  }
15
44
 
16
45
  export function getAllInventoryTypes(): InventoryTypeDefinition[] {