fence curve: single-undo dance in commit — fix undo skipping past drag
The previous commit() just returned true, relying on createDragSession.terminate() to resumeHistory. But that alone never captures the drag in zundo's pastStates — the pause window's mutations are skipped entirely. After commit, Ctrl-Z jumped past the curve *and* past the prior fence creation. Adds the same dance now used by move-endpoint: restoreAll → resumeHistory → re-apply the final draft. Zundo records one undo step for the whole drag. cancel() becomes a no-op (orchestrator's restoreAll covers it). 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
05efa25edb
commit
5e67ffd4e8
@@ -25,23 +25,15 @@ import {
|
|||||||
* - **snap**: optional grid snap unless `modifiers.shift` (free place).
|
* - **snap**: optional grid snap unless `modifiers.shift` (free place).
|
||||||
* - **apply**: write the new curveOffset onto the fence node. Returns
|
* - **apply**: write the new curveOffset onto the fence node. Returns
|
||||||
* the dirty IDs the cascade resolver should walk.
|
* the dirty IDs the cascade resolver should walk.
|
||||||
* - **commit**: returns true → drag finalizes. `useDragAction`
|
* - **commit**: single-undo dance — `restoreAll` → `resumeHistory` →
|
||||||
* resumes history so the post-pause final write lands as a single
|
* re-apply final draft so zundo captures the whole drag as one
|
||||||
* undo step.
|
* Ctrl-Z step. Rejected when the offset didn't actually change.
|
||||||
* - **cancel**: restore the original curveOffset. Called on Esc /
|
* - **cancel**: no-op — `createDragSession.cancel()` calls
|
||||||
* component unmount / commit-returns-false.
|
* `scene.restoreAll()` via the snapshot.
|
||||||
*
|
*
|
||||||
* Pure data: trivially unit-testable, doesn't import React. The
|
* Pure data: trivially unit-testable, doesn't import React.
|
||||||
* orchestrator (`createDragSession`) handles pauseHistory / resumeHistory
|
|
||||||
* automatically.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const GRID_STEP = 0.5
|
|
||||||
|
|
||||||
function snapScalar(value: number): number {
|
|
||||||
return Math.round(value / GRID_STEP) * GRID_STEP
|
|
||||||
}
|
|
||||||
|
|
||||||
type CurveFenceCtx = {
|
type CurveFenceCtx = {
|
||||||
nodeId: AnyNodeId
|
nodeId: AnyNodeId
|
||||||
originalCurveOffset: number
|
originalCurveOffset: number
|
||||||
@@ -95,19 +87,24 @@ export const curveFenceDragAction: DragAction<CurveFenceCtx, CurveFenceDraft> =
|
|||||||
return [ctx.nodeId]
|
return [ctx.nodeId]
|
||||||
},
|
},
|
||||||
|
|
||||||
commit: (_draft, _ctx, _scene) => {
|
commit: (draft, ctx, scene) => {
|
||||||
// Returning true tells the orchestrator to finalize. The orchestrator
|
// Reject when the offset didn't actually change — createDragSession
|
||||||
// resumes history and re-applies the final draft — yields a single
|
// will fall through to cancel + scene.restoreAll() (no zundo entry).
|
||||||
// undo step for the whole drag.
|
if (draft.curveOffset === ctx.originalCurveOffset) return false
|
||||||
|
|
||||||
|
// Single-undo dance: revert via the snapshot (paused history → no
|
||||||
|
// zundo record), resume history, then re-apply the final draft so
|
||||||
|
// zundo captures the whole drag as one undo step. Without this the
|
||||||
|
// pause window's mutations never reach pastStates and Ctrl-Z jumps
|
||||||
|
// past the drag back to the state before activation.
|
||||||
|
scene.restoreAll()
|
||||||
|
scene.resumeHistory()
|
||||||
|
scene.update(ctx.nodeId, { curveOffset: draft.curveOffset } as Partial<AnyNode>)
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel: (ctx, scene) => {
|
cancel: (_ctx, _scene) => {
|
||||||
// Restore the original curve offset (history was paused, so nothing
|
// No-op — createDragSession.cancel() calls scene.restoreAll() which
|
||||||
// intermediate is on the undo stack).
|
// puts every touched node back via the snapshot.
|
||||||
scene.update(ctx.nodeId, {
|
|
||||||
curveOffset: ctx.originalCurveOffset,
|
|
||||||
} as Partial<AnyNode>)
|
|
||||||
scene.markDirty(ctx.nodeId)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user