minecraft-inventory 0.1.41 → 0.1.43

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/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # minecraft-inventory
2
2
 
3
+ DEMO: https://minecraft-inventory-npm.vercel.app/
4
+
3
5
  A flexible, scalable React library for rendering Minecraft inventory GUIs. Designed for integration into real Minecraft clients (e.g., mineflayer bots, web clients) without using CSS `transform: scale` — all sizing is driven by CSS custom properties.
4
6
 
5
7
  ## Features
@@ -10,7 +12,7 @@ A flexible, scalable React library for rendering Minecraft inventory GUIs. Desig
10
12
  - Tooltips that follow the cursor, matching the original Minecraft style
11
13
  - Full keyboard support: number keys (1-9) to swap hotbar, Q to drop, double-click to collect, scroll wheel to pick/place
12
14
  - Mobile support: tap-to-focus inventory interactions plus long-press slot actions (pick half / custom amount / drop one / drop all)
13
- - Optional JEI (item browser) panel on the left or right
15
+ - Optional JEI (item browser) panel on right
14
16
  - Bot connector layer — plugs into a mineflayer bot or any custom server connection
15
17
  - Demo connector with action logging for local development
16
18
  - Extendable registry — add new inventory types in one place
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecraft-inventory",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "release": {
@@ -16,6 +16,17 @@
16
16
  "files": [
17
17
  "src"
18
18
  ],
19
+ "homepage": "https://mcraft.fun",
20
+ "keywords": [
21
+ "minecraft",
22
+ "minecraft-ui",
23
+ "minecraft-inventory",
24
+ "inventory",
25
+ "vanilla",
26
+ "react",
27
+ "typescript",
28
+ "web"
29
+ ],
19
30
  "devDependencies": {
20
31
  "@rsbuild/core": "^1.7.3",
21
32
  "@rsbuild/plugin-react": "^1.4.6",
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useState } from 'react'
1
+ import React, { useCallback, useEffect, useState } from 'react'
2
2
  import { useCloseOnWClick } from '../../hooks/useCloseOnWClick'
3
3
  import { useInventoryContext } from '../../context/InventoryContext'
4
4
  import { useScale } from '../../context/ScaleContext'
@@ -94,7 +94,7 @@ export function InventoryOverlay({
94
94
  noWatermark = false,
95
95
  onOpenSettings,
96
96
  }: InventoryOverlayProps) {
97
- const { heldItem, sendAction, setHeldItem, focusedSlot, setFocusedSlot } = useInventoryContext()
97
+ const { heldItem, sendAction, setHeldItem, focusedSlot, setFocusedSlot, hoveredSlot, getSlot } = useInventoryContext()
98
98
  const { scale } = useScale()
99
99
  const [settingsHovered, setSettingsHovered] = useState(false)
100
100
 
@@ -136,8 +136,30 @@ export function InventoryOverlay({
136
136
  pushRecipeFrame({ item, mode, guides, guideIndex: 0 })
137
137
  }, [jeiOnGetRecipes, jeiOnGetUsages, pushRecipeFrame])
138
138
 
139
+ // R / U while hovering a regular inventory slot (not JEI, not recipe view)
140
+ useEffect(() => {
141
+ if (!jeiOnGetRecipes && !jeiOnGetUsages) return
142
+ const handler = (e: KeyboardEvent) => {
143
+ if (e.code !== 'KeyR' && e.code !== 'KeyU') return
144
+ const target = e.target as Element | null
145
+ const tagName = (target as { tagName?: string })?.tagName ?? ''
146
+ if (tagName === 'INPUT' || tagName === 'TEXTAREA' || tagName === 'SELECT' || (target as { isContentEditable?: boolean })?.isContentEditable) return
147
+ if (recipeNavStack.length > 0) return // RecipeInventoryView handles its own R/U
148
+ if (hoveredSlot === null || hoveredSlot < 0) return // JEI slots handled by JEI.tsx
149
+ const slot = getSlot(hoveredSlot)
150
+ if (!slot?.item) return
151
+ const item = slot.item
152
+ handleRecipePushFrame(
153
+ { type: item.type, name: item.name ?? '', displayName: item.displayName ?? item.name ?? `Item #${item.type}`, count: item.count, metadata: item.metadata },
154
+ e.code === 'KeyR' ? 'recipes' : 'usages',
155
+ )
156
+ }
157
+ window.addEventListener('keydown', handler)
158
+ return () => window.removeEventListener('keydown', handler)
159
+ }, [jeiOnGetRecipes, jeiOnGetUsages, recipeNavStack.length, hoveredSlot, getSlot, handleRecipePushFrame])
160
+
139
161
  // Fires for any click that isn't stopped by an interactive panel (inventory, hotbar, JEI, etc.)
140
- const handleBackdropClick = useCallback((e: React.MouseEvent) => {
162
+ const handleBackdropClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
141
163
  // Only handle left (drop all) and right (drop one) mouse buttons
142
164
  if (e.button !== 0 && e.button !== 2) return
143
165
  if (heldItem) {
@@ -316,6 +338,7 @@ export function InventoryOverlay({
316
338
  href="https://www.npmjs.com/package/minecraft-inventory"
317
339
  target="_blank"
318
340
  rel="noopener noreferrer"
341
+ onMouseDown={(e) => e.stopPropagation()}
319
342
  onClick={(e) => e.stopPropagation()}
320
343
  style={{
321
344
  position: 'absolute',
@@ -330,7 +353,7 @@ export function InventoryOverlay({
330
353
  lineHeight: 1,
331
354
  }}
332
355
  >
333
- INV 0.1.41
356
+ INV 0.1.43
334
357
  </a>
335
358
  )}
336
359
 
@@ -167,6 +167,7 @@ export function Notes({
167
167
  </span>
168
168
  <button
169
169
  type="button"
170
+ onMouseDown={(e) => e.stopPropagation()}
170
171
  onClick={(e) => {
171
172
  e.stopPropagation()
172
173
  removeNote(note.id)
@@ -205,6 +206,7 @@ export function Notes({
205
206
  value={draft}
206
207
  rows={2}
207
208
  autoFocus
209
+ onMouseDown={(e) => e.stopPropagation()}
208
210
  onClick={(e) => e.stopPropagation()}
209
211
  onChange={(e) => {
210
212
  setDraft(e.target.value.replace(/[\r\n]/g, ''))
@@ -235,6 +237,7 @@ export function Notes({
235
237
  {showInput && (
236
238
  <button
237
239
  type="button"
240
+ onMouseDown={(e) => e.stopPropagation()}
238
241
  onClick={(e) => {
239
242
  e.stopPropagation()
240
243
  setShowInput(false)
@@ -257,6 +260,7 @@ export function Notes({
257
260
  )}
258
261
  <button
259
262
  type="button"
263
+ onMouseDown={(e) => e.stopPropagation()}
260
264
  onClick={(e) => {
261
265
  e.stopPropagation()
262
266
  handleAddButtonClick()
@@ -6,6 +6,14 @@ import { useScale } from '../../context/ScaleContext'
6
6
  import { useTextures } from '../../context/TextureContext'
7
7
  import { getInventoryType } from '../../registry'
8
8
 
9
+ function isTypingTarget(target: EventTarget | null): boolean {
10
+ if (!target || !(target instanceof HTMLElement)) return false
11
+ const tag = target.tagName
12
+ if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true
13
+ if (target.isContentEditable) return true
14
+ return false
15
+ }
16
+
9
17
  interface RecipeInventoryViewProps {
10
18
  navStack: RecipeNavFrame[]
11
19
  onBack: () => void
@@ -96,6 +104,7 @@ export function RecipeInventoryView({
96
104
  useEffect(() => {
97
105
  const handler = (e: KeyboardEvent) => {
98
106
  if (e.code !== 'KeyR' && e.code !== 'KeyU') return
107
+ if (isTypingTarget(e.target)) return
99
108
  const item = hoveredItemRef.current
100
109
  if (!item) return
101
110
  onPushFrame(item, e.code === 'KeyR' ? 'recipes' : 'usages')
@@ -104,10 +113,13 @@ export function RecipeInventoryView({
104
113
  return () => window.removeEventListener('keydown', handler)
105
114
  }, [onPushFrame])
106
115
 
107
- // Escape = go back one level
116
+ // Escape / Backspace = go back one frame (Backspace on root frame closes recipe view)
108
117
  useEffect(() => {
109
118
  const handler = (e: KeyboardEvent) => {
110
- if (e.code === 'Escape') onBack()
119
+ if (e.code !== 'Escape' && e.code !== 'Backspace') return
120
+ if (isTypingTarget(e.target)) return
121
+ onBack()
122
+ if (e.code === 'Backspace') e.preventDefault()
111
123
  }
112
124
  window.addEventListener('keydown', handler)
113
125
  return () => window.removeEventListener('keydown', handler)