useDragAction: activation-click grace + curve fence commit sfx
The legacy CurveFenceTool ignored grid:click for 150ms after mount — otherwise the very click that activates a tool (e.g. the floating menu "curve" button) cascades through the R3F drei <Html> portal into the grid, fires grid:click on the just-mounted tool, and commits the drag before any preview move runs. The new useDragAction was missing this guard, so the Stage D fence curve port "click → place sfx → exit" without ever letting the user adjust. Adds `activationGraceMs` (default 150) on useDragAction; ports the sfx:item-place commit emission into FenceCurveTool so the kind-owned tool matches legacy UX. Same guard will cover the upcoming Stage D ports (endpoint move, whole-fence move, placement, plus slab/ceiling/wall D). 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
8ca9686b27
commit
1082b62552
@@ -42,6 +42,14 @@ export type UseDragActionArgs<Ctx, Draft> = {
|
|||||||
onCommit?: () => void
|
onCommit?: () => void
|
||||||
/** Fires once after `action.cancel` (Esc, unmount, or commit-returns-false). */
|
/** Fires once after `action.cancel` (Esc, unmount, or commit-returns-false). */
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
|
/**
|
||||||
|
* Milliseconds after activation during which `grid:click` is swallowed.
|
||||||
|
* Stops the very click that mounted this tool (a DOM button or 3D
|
||||||
|
* handle elsewhere) from cascading into the grid and immediately
|
||||||
|
* committing the drag. Defaults to 150ms — matches the legacy guard
|
||||||
|
* used by every kind-owned tool entered via a click.
|
||||||
|
*/
|
||||||
|
activationGraceMs?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -72,12 +80,21 @@ export function useDragAction<Ctx, Draft>(args: UseDragActionArgs<Ctx, Draft>) {
|
|||||||
|
|
||||||
session.start(argsRef.current.initial)
|
session.start(argsRef.current.initial)
|
||||||
|
|
||||||
|
const activatedAt = Date.now()
|
||||||
|
const graceMs = argsRef.current.activationGraceMs ?? 150
|
||||||
|
|
||||||
const onMove = (event: GridEvent) => {
|
const onMove = (event: GridEvent) => {
|
||||||
const point: readonly [number, number] = [event.localPosition[0], event.localPosition[2]]
|
const point: readonly [number, number] = [event.localPosition[0], event.localPosition[2]]
|
||||||
session.move(point, modifiersFromGridEvent(event))
|
session.move(point, modifiersFromGridEvent(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = (_event: GridEvent) => {
|
const onClick = (event: GridEvent) => {
|
||||||
|
// Swallow the click that mounted this tool — otherwise the very
|
||||||
|
// first grid:click cascades into commit() before any move().
|
||||||
|
if (Date.now() - activatedAt < graceMs) {
|
||||||
|
event.nativeEvent?.stopPropagation?.()
|
||||||
|
return
|
||||||
|
}
|
||||||
session.commit()
|
session.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import { type FenceNode, getWallMidpointHandlePoint, useScene } from '@pascal-app/core'
|
import { type FenceNode, getWallMidpointHandlePoint, useScene } from '@pascal-app/core'
|
||||||
import { CursorSphere, useDragAction, useEditor } from '@pascal-app/editor'
|
import { CursorSphere, triggerSFX, useDragAction, useEditor } from '@pascal-app/editor'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
import { useViewer } from '@pascal-app/viewer'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { curveFenceDragAction } from './actions/curve'
|
import { curveFenceDragAction } from './actions/curve'
|
||||||
@@ -31,7 +31,8 @@ export const FenceCurveTool: React.FC<{ node: FenceNode }> = ({ node }) => {
|
|||||||
initialHandle.y,
|
initialHandle.y,
|
||||||
])
|
])
|
||||||
|
|
||||||
const exitCurveMode = () => {
|
const exitCurveMode = (committed: boolean) => {
|
||||||
|
if (committed) triggerSFX('sfx:item-place')
|
||||||
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
||||||
useEditor.getState().setCurvingFence(null)
|
useEditor.getState().setCurvingFence(null)
|
||||||
}
|
}
|
||||||
@@ -46,8 +47,8 @@ export const FenceCurveTool: React.FC<{ node: FenceNode }> = ({ node }) => {
|
|||||||
// placeholder until the first grid:move fires.
|
// placeholder until the first grid:move fires.
|
||||||
point: [initialHandle.x, initialHandle.y],
|
point: [initialHandle.x, initialHandle.y],
|
||||||
},
|
},
|
||||||
onCommit: exitCurveMode,
|
onCommit: () => exitCurveMode(true),
|
||||||
onCancel: exitCurveMode,
|
onCancel: () => exitCurveMode(false),
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mirror the active curveOffset back into the cursor position. The
|
// Mirror the active curveOffset back into the cursor position. The
|
||||||
|
|||||||
Reference in New Issue
Block a user