65 lines
2.3 KiB
TypeScript

'use client'
import type { AnyNode, AnyNodeId } from '@pascal-app/core'
import { useScene } from '@pascal-app/core'
import { useViewer } from '@pascal-app/viewer'
/**
* Resolved current selection — selected node IDs plus a convenience
* lookup into the live scene store. Returned shape is intentionally
* narrow: hosts that need richer per-node state can compose this with
* `useScene()` themselves.
*/
export type Selection = {
/** Multi-select node IDs (drives the rest of the editor's UI). */
selectedIds: AnyNodeId[]
/** Currently active building context — surfaces / palettes filter on this. */
buildingId: AnyNodeId | null
/** Currently active level context. */
levelId: AnyNodeId | null
/** Currently active zone context. */
zoneId: AnyNodeId | null
/**
* Resolved nodes for `selectedIds`. Missing entries (deleted nodes) are
* filtered out, so the array length may be ≤ `selectedIds.length`.
*/
selectedNodes: AnyNode[]
/**
* The single selected node, or `null` when zero or multiple nodes are
* selected. Useful for "save as preset" / inspector gating where the
* UI only makes sense for a unique selection.
*/
selectedNode: AnyNode | null
}
/**
* Subscribe to the current selection. Equivalent to reading from
* `useViewer().selection` plus a live `useScene()` lookup, packaged as
* a single hook so consumers building their own shells (community,
* standalone editor app, embedders) don't have to learn the two
* separate stores.
*
* Selection state intentionally lives in `useViewer` (it tracks the
* camera / visibility hierarchy: building → level → zone → nodes), not
* `useScene` — see `wiki/architecture/scene-registry.md`.
*/
export function useSelection(): Selection {
const selection = useViewer((s) => s.selection)
const nodes = useScene((s) => s.nodes)
const selectedIds = selection.selectedIds as AnyNodeId[]
const selectedNodes = selectedIds
.map((id) => nodes[id])
.filter((n): n is AnyNode => n !== undefined)
const selectedNode = selectedNodes.length === 1 ? (selectedNodes[0] ?? null) : null
return {
selectedIds,
buildingId: (selection.buildingId ?? null) as AnyNodeId | null,
levelId: (selection.levelId ?? null) as AnyNodeId | null,
zoneId: (selection.zoneId ?? null) as AnyNodeId | null,
selectedNodes,
selectedNode,
}
}