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).
|
||||
* - **apply**: write the new curveOffset onto the fence node. Returns
|
||||
* the dirty IDs the cascade resolver should walk.
|
||||
* - **commit**: returns true → drag finalizes. `useDragAction`
|
||||
* resumes history so the post-pause final write lands as a single
|
||||
* undo step.
|
||||
* - **cancel**: restore the original curveOffset. Called on Esc /
|
||||
* component unmount / commit-returns-false.
|
||||
* - **commit**: single-undo dance — `restoreAll` → `resumeHistory` →
|
||||
* re-apply final draft so zundo captures the whole drag as one
|
||||
* Ctrl-Z step. Rejected when the offset didn't actually change.
|
||||
* - **cancel**: no-op — `createDragSession.cancel()` calls
|
||||
* `scene.restoreAll()` via the snapshot.
|
||||
*
|
||||
* Pure data: trivially unit-testable, doesn't import React. The
|
||||
* orchestrator (`createDragSession`) handles pauseHistory / resumeHistory
|
||||
* automatically.
|
||||
* Pure data: trivially unit-testable, doesn't import React.
|
||||
*/
|
||||
|
||||
const GRID_STEP = 0.5
|
||||
|
||||
function snapScalar(value: number): number {
|
||||
return Math.round(value / GRID_STEP) * GRID_STEP
|
||||
}
|
||||
|
||||
type CurveFenceCtx = {
|
||||
nodeId: AnyNodeId
|
||||
originalCurveOffset: number
|
||||
@@ -95,19 +87,24 @@ export const curveFenceDragAction: DragAction<CurveFenceCtx, CurveFenceDraft> =
|
||||
return [ctx.nodeId]
|
||||
},
|
||||
|
||||
commit: (_draft, _ctx, _scene) => {
|
||||
// Returning true tells the orchestrator to finalize. The orchestrator
|
||||
// resumes history and re-applies the final draft — yields a single
|
||||
// undo step for the whole drag.
|
||||
commit: (draft, ctx, scene) => {
|
||||
// Reject when the offset didn't actually change — createDragSession
|
||||
// will fall through to cancel + scene.restoreAll() (no zundo entry).
|
||||
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
|
||||
},
|
||||
|
||||
cancel: (ctx, scene) => {
|
||||
// Restore the original curve offset (history was paused, so nothing
|
||||
// intermediate is on the undo stack).
|
||||
scene.update(ctx.nodeId, {
|
||||
curveOffset: ctx.originalCurveOffset,
|
||||
} as Partial<AnyNode>)
|
||||
scene.markDirty(ctx.nodeId)
|
||||
cancel: (_ctx, _scene) => {
|
||||
// No-op — createDragSession.cancel() calls scene.restoreAll() which
|
||||
// puts every touched node back via the snapshot.
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user