Merge remote-tracking branch 'origin/main' into feat/paint-slots

# Conflicts:
#	packages/core/src/store/use-scene.ts
#	packages/editor/src/components/editor/index.tsx
This commit is contained in:
Wassim SAMAD
2026-06-18 12:22:26 -04:00
415 changed files with 22805 additions and 1414 deletions
+1 -1
View File
@@ -322,7 +322,7 @@ export const itemDefinition: NodeDefinition<typeof ItemNode> = {
presentation: {
label: 'Item',
description: 'A catalog-backed item (furniture, fixtures, decorations).',
icon: { kind: 'url', src: '/icons/item.png' },
icon: { kind: 'url', src: '/icons/item.webp' },
paletteSection: 'furnish',
paletteOrder: 10,
},
+28 -2
View File
@@ -1,6 +1,6 @@
'use client'
import type { ItemNode } from '@pascal-app/core'
import { type AnyNodeId, type ItemNode, useScene } from '@pascal-app/core'
import {
type PlacementState,
triggerSFX,
@@ -67,6 +67,32 @@ function getInitialState(node: ItemNode): PlacementState {
shelfId: null,
}
}
// A floor item resting on a host surface (table / counter / shelf) starts in
// that surface, not 'floor', so the first pointer move runs the surface move
// handler — which preserves the grab offset — instead of a fresh `enter()`
// that snaps the item's origin under the cursor. Without this the item
// teleports the instant it's grabbed.
const parent = node.parentId ? useScene.getState().nodes[node.parentId as AnyNodeId] : undefined
if (parent?.type === 'item') {
return {
surface: 'item-surface',
wallId: null,
roofSegmentId: null,
ceilingId: null,
surfaceItemId: node.parentId,
shelfId: null,
}
}
if (parent?.type === 'shelf') {
return {
surface: 'shelf-surface',
wallId: null,
roofSegmentId: null,
ceilingId: null,
surfaceItemId: null,
shelfId: node.parentId,
}
}
return {
surface: 'floor',
wallId: null,
@@ -102,7 +128,7 @@ export function MoveItemTool({ node }: { node: ItemNode }) {
: getInitialState(node),
// Preserve the original item's scale so Y-position calculations use the correct height.
defaultScale: isNew ? node.scale : undefined,
preserveFloorDragOffset: true,
preserveDragOffset: true,
initDraft: (gridPosition) => {
if (isNew) {
// Duplicate: floor items get a draft immediately; wall/ceiling
+1 -1
View File
@@ -100,7 +100,7 @@ export default function ItemPanel() {
return (
<PanelWrapper
icon={node.asset.thumbnail || '/icons/furniture.png'}
icon={node.asset.thumbnail || '/icons/furniture.webp'}
onClose={handleClose}
title={node.name || node.asset.name}
width={300}