fix: zone grid snapping, shelf position panel, deselect on item placement (#394)

* fix: zone grid snapping, shelf position panel, deselect on item placement

- Zone tool reads the editor's gridSnapStep (0.5/0.25/0.1/0.05) instead
  of a hardcoded 0.5 for both cursor move and click snapping.
- Shelf inspector gains a Position group (vec3 X/Y/Z sliders), matching
  the item panel.
- Item catalog clears the viewer selection before arming placement so
  shortcuts (rotate & co) don't hit both the ghost and the selected node.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: move level display naming into @pascal-app/core

getDefaultLevelName / getLevelDisplayName ("Ground Floor" / "Floor N" /
"Basement N") lived in packages/editor's internal lib, so viewer-only
surfaces couldn't reach them and fell back to hand-rolled "Level N"
labels. The helpers are pure domain logic, so they move to core and
export from its barrel; the editor package's seven call sites now import
them from there.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-10 13:56:24 -04:00
committed by GitHub
co-authored by Claude Fable 5
parent 7539387ff5
commit d36e8fcc78
12 changed files with 20 additions and 11 deletions
+1
View File
@@ -61,6 +61,7 @@ export {
isOperationDoorType,
SECTIONAL_GARAGE_RENDER_OPEN_SCALE,
} from './lib/door-operation'
export { getDefaultLevelName, getLevelDisplayName } from './lib/level-name'
export {
type Point2D as PolygonPoint2D,
pointInPolygon as pointInPolygon2D,
@@ -1,4 +1,4 @@
import type { LevelNode } from '@pascal-app/core'
import type { LevelNode } from '../schema'
export function getDefaultLevelName(level: number): string {
if (level === 0) return 'Ground Floor'
@@ -184,7 +184,7 @@ export const ZoneTool: React.FC = () => {
const [gridX, gridZ] = snapWorldXZForActiveBuilding(
event.position[0],
event.position[2],
0.5,
useEditor.getState().gridSnapStep,
).local
cursorPosition = [gridX, gridZ]
levelYRef.current = event.localPosition[1]
@@ -217,7 +217,7 @@ export const ZoneTool: React.FC = () => {
const [gridX, gridZ] = snapWorldXZForActiveBuilding(
event.position[0],
event.position[2],
0.5,
useEditor.getState().gridSnapStep,
).local
let clickPoint: [number, number] = [gridX, gridZ]
@@ -13,7 +13,7 @@ import { useViewer } from '@pascal-app/viewer'
import { Check, ChevronDown, Eye, EyeOff, Layers2, Plus, Trash2 } from 'lucide-react'
import { useCallback, useRef, useState } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { getLevelDisplayName } from '../../../lib/level-name'
import { getLevelDisplayName } from '@pascal-app/core'
import { createLocalGuideImage } from '../../../lib/local-guide-image'
import { cn } from '../../../lib/utils'
import useEditor, { type GridSnapStep } from '../../../store/use-editor'
@@ -10,7 +10,7 @@ import { useEffect, useState } from 'react'
import { create } from 'zustand'
import { useShallow } from 'zustand/shallow'
import { Dialog, DialogContent, DialogTitle } from './../../../components/ui/primitives/dialog'
import { getLevelDisplayName } from '../../../lib/level-name'
import { getLevelDisplayName } from '@pascal-app/core'
import { useCommandRegistry } from '../../../store/use-command-registry'
import { usePaletteViewRegistry } from '../../../store/use-palette-view-registry'
@@ -41,7 +41,7 @@ import {
buildLevelDuplicateCreateOps,
type LevelDuplicatePreset,
} from '../../lib/level-duplication'
import { getDefaultLevelName, getLevelDisplayName } from '../../lib/level-name'
import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
import { deleteLevelWithFallbackSelection } from '../../lib/level-selection'
import {
getEditorClipboardSnapshot,
@@ -1,7 +1,7 @@
'use client'
import type { AssetInput } from '@pascal-app/core'
import { resolveCdnUrl } from '@pascal-app/viewer'
import { resolveCdnUrl, useViewer } from '@pascal-app/viewer'
import Image from 'next/image'
import { useEffect } from 'react'
import {
@@ -96,6 +96,10 @@ export function ItemCatalog({
key={index}
onClick={() => {
triggerSFX('sfx:menu-click')
// Drop the current selection before arming placement — keeping
// it would route shortcuts (rotate & co) to both the ghost and
// the selected node.
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
setSelectedItem(item)
setTool('item')
setMode('build')
@@ -3,7 +3,7 @@
import type { LevelNode } from '@pascal-app/core'
import { useEffect, useState } from 'react'
import type { LevelDuplicatePreset } from '../../lib/level-duplication'
import { getLevelDisplayName } from '../../lib/level-name'
import { getLevelDisplayName } from '@pascal-app/core'
import { cn } from '../../lib/utils'
import {
Dialog,
@@ -36,7 +36,7 @@ import {
buildLevelDuplicateCreateOps,
type LevelDuplicatePreset,
} from './../../../../../lib/level-duplication'
import { getDefaultLevelName } from './../../../../../lib/level-name'
import { getDefaultLevelName } from '@pascal-app/core'
import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
import { createLocalGuideImage } from './../../../../../lib/local-guide-image'
import { cn } from './../../../../../lib/utils'
@@ -3,7 +3,7 @@ import { useViewer } from '@pascal-app/viewer'
import { Layers } from 'lucide-react'
import { memo, useCallback, useState } from 'react'
import { useShallow } from 'zustand/react/shallow'
import { getDefaultLevelName } from '../../../../../lib/level-name'
import { getDefaultLevelName } from '@pascal-app/core'
import { InlineRenameInput } from './inline-rename-input'
import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
import { TreeNodeActions } from './tree-node-actions'
@@ -6,6 +6,7 @@ import {
type AnyNodeId,
type BuildingNode,
emitter,
getLevelDisplayName,
type LevelNode,
useScene,
type ZoneNode,
@@ -30,7 +31,6 @@ import {
} from 'lucide-react'
import Link from 'next/link'
import { useShallow } from 'zustand/react/shallow'
import { getLevelDisplayName } from '../lib/level-name'
import { cn } from '../lib/utils'
import { ActionButton } from './ui/action-menu/action-button'
import {
+4
View File
@@ -76,5 +76,9 @@ export const shelfParametrics: ParametricDescriptor<ShelfNode> = {
{ key: 'height', kind: 'number', unit: 'm', min: 0.05, max: 2.5, step: 0.05 },
],
},
{
label: 'Position',
fields: [{ key: 'position', kind: 'vec3' }],
},
],
}