Phase 5 Stage D: fence curve affordance → registry DragAction
First Stage D port — `CurveFenceTool` (178 LoC legacy) split into a pure `DragAction` primitive (`packages/nodes/src/fence/actions/curve.ts`) plus a thin React wrapper (`packages/nodes/src/fence/curve-tool.tsx`) that feeds it through `useDragAction`. The kind declares the affordance via `def.affordanceTools.curve`; ToolManager lazy-loads it at runtime when `useEditor.curvingFence` activates. Falls back to the legacy `CurveFenceTool` for any kind that hasn't been ported. The lazy-load dispatch dodges the editor→nodes circular dep (nodes already depends on editor for `useDragAction` + `CursorSphere`). Establishes the pattern for the remaining fence affordances (`MoveFenceEndpoint`, `MoveFence`, placement) and for slab/ceiling/wall D ports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
95645d8e37
commit
8ca9686b27
@@ -49,6 +49,29 @@ function getRegistryTool(tool: Tool | null): ComponentType | null {
|
||||
return Comp
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-loads the kind's drag-affordance component from
|
||||
* `def.affordanceTools[name]`. Lets editor consume kind-owned tools
|
||||
* from `@pascal-app/nodes` without a static import (which would create
|
||||
* a circular dep: nodes already depends on editor).
|
||||
*
|
||||
* Returns null when the kind doesn't declare the affordance — caller
|
||||
* renders the legacy fallback in that case.
|
||||
*/
|
||||
function getRegistryAffordanceTool(
|
||||
kind: string,
|
||||
affordance: string,
|
||||
): ComponentType<{ node: any }> | null {
|
||||
const def = nodeRegistry.get(kind)
|
||||
const loader = def?.affordanceTools?.[affordance]
|
||||
if (!loader) return null
|
||||
const cached = lazyToolCache.get(loader)
|
||||
if (cached) return cached as ComponentType<{ node: any }>
|
||||
const Comp = lazy(loader as () => Promise<{ default: ComponentType<{ node: any }> }>)
|
||||
lazyToolCache.set(loader, Comp as unknown as ComponentType)
|
||||
return Comp
|
||||
}
|
||||
|
||||
const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
|
||||
site: {
|
||||
'property-line': SiteBoundaryEditor,
|
||||
@@ -191,7 +214,17 @@ export const ToolManager: React.FC = () => {
|
||||
{movingWallEndpoint && <MoveWallEndpointTool target={movingWallEndpoint} />}
|
||||
{movingFenceEndpoint && <MoveFenceEndpointTool target={movingFenceEndpoint} />}
|
||||
{curvingWall && <CurveWallTool node={curvingWall} />}
|
||||
{curvingFence && <CurveFenceTool node={curvingFence} />}
|
||||
{curvingFence &&
|
||||
(() => {
|
||||
const RegistryAffordance = getRegistryAffordanceTool(curvingFence.type, 'curve')
|
||||
return RegistryAffordance ? (
|
||||
<Suspense fallback={null}>
|
||||
<RegistryAffordance node={curvingFence} />
|
||||
</Suspense>
|
||||
) : (
|
||||
<CurveFenceTool node={curvingFence} />
|
||||
)
|
||||
})()}
|
||||
{movingNode && movingNode.type !== 'building' && (
|
||||
<MoveTool
|
||||
onNodeMoved={handlePlacedNodeSelected}
|
||||
|
||||
@@ -27,6 +27,10 @@ export type { SidebarTab } from './components/ui/sidebar/tab-bar'
|
||||
export type { PresetsAdapter, PresetsTab } from './contexts/presets-context'
|
||||
export { PresetsProvider } from './contexts/presets-context'
|
||||
export type { SaveStatus } from './hooks/use-auto-save'
|
||||
// useDragAction is the React-side glue for the registry's DragAction
|
||||
// primitive. Public so registry-driven kinds (Phase 5+ Stage D ports)
|
||||
// can express their affordances declaratively in their own folder.
|
||||
export { type UseDragActionArgs, useDragAction } from './hooks/use-drag-action'
|
||||
export type { SceneGraph } from './lib/scene'
|
||||
export { applySceneGraphToEditor } from './lib/scene'
|
||||
export { triggerSFX } from './lib/sfx-bus'
|
||||
|
||||
Reference in New Issue
Block a user