import { type BuildingEvent, type BuildingNode, type CeilingEvent, type CeilingNode, type DoorEvent, type DoorNode, type EventSuffix, emitter, type FenceEvent, type FenceNode, type ItemEvent, type ItemNode, type LevelEvent, type LevelNode, type RoofEvent, type RoofNode, type RoofSegmentEvent, type RoofSegmentNode, type SiteEvent, type SiteNode, type SlabEvent, type SlabNode, type StairEvent, type StairNode, type StairSegmentEvent, type StairSegmentNode, type WallEvent, type WallNode, type WindowEvent, type WindowNode, type ZoneEvent, type ZoneNode, } from '@pascal-app/core' import type { ThreeEvent } from '@react-three/fiber' import useViewer from '../store/use-viewer' type NodeConfig = { site: { node: SiteNode; event: SiteEvent } item: { node: ItemNode; event: ItemEvent } wall: { node: WallNode; event: WallEvent } fence: { node: FenceNode; event: FenceEvent } building: { node: BuildingNode; event: BuildingEvent } level: { node: LevelNode; event: LevelEvent } zone: { node: ZoneNode; event: ZoneEvent } slab: { node: SlabNode; event: SlabEvent } ceiling: { node: CeilingNode; event: CeilingEvent } roof: { node: RoofNode; event: RoofEvent } 'roof-segment': { node: RoofSegmentNode; event: RoofSegmentEvent } stair: { node: StairNode; event: StairEvent } 'stair-segment': { node: StairSegmentNode; event: StairSegmentEvent } window: { node: WindowNode; event: WindowEvent } door: { node: DoorNode; event: DoorEvent } } type NodeType = keyof NodeConfig export function useNodeEvents(node: NodeConfig[T]['node'], type: T) { const emit = (suffix: EventSuffix, e: ThreeEvent) => { const eventKey = `${type}:${suffix}` as `${T}:${EventSuffix}` const localPoint = e.object.worldToLocal(e.point.clone()) const payload = { node, position: [e.point.x, e.point.y, e.point.z], localPosition: [localPoint.x, localPoint.y, localPoint.z], normal: e.face ? [e.face.normal.x, e.face.normal.y, e.face.normal.z] : undefined, stopPropagation: () => e.stopPropagation(), nativeEvent: e, } as NodeConfig[T]['event'] emitter.emit(eventKey, payload) } return { onPointerDown: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return if (e.button !== 0) return emit('pointerdown', e) }, onPointerUp: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return if (e.button !== 0) return emit('pointerup', e) // Synthesize a click event on pointer up to be more forgiving than R3F's default onClick // which often fails if the mouse moves even 1 pixel. emit('click', e) }, onClick: (e: ThreeEvent) => { // Disable default R3F click since we synthesize it on pointerup // This prevents double-clicks from firing twice. }, onPointerEnter: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return emit('enter', e) }, onPointerLeave: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return emit('leave', e) }, onPointerMove: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return emit('move', e) }, onDoubleClick: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return emit('double-click', e) }, onContextMenu: (e: ThreeEvent) => { if (useViewer.getState().cameraDragging) return emit('context-menu', e) }, } }