plans/issues/issue-1827.md
Executor instructions: Follow this plan step by step. Run every verification command and confirm the expected result before moving on. If anything in "STOP conditions" occurs, stop and report — do not improvise. When done, update this plan's row in
plans/issues/README.md.Drift check (run first):
gh api repos/motiondivision/motion/issues/1827 --jq .state→open(ifclosed, mark this plan DONE-ALREADY and stop).git diff --stat 42bfbe3ed..HEAD -- packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts packages/framer-motion/cypress/integration/ dev/react/src/tests/— ifVisualElementDragControls.tschanged, re-verify the "Current state" excerpts before proceeding; on a mismatch, STOP.
42bfbe3ed, 2026-06-11Issue #1827 (2022): with dragConstraints set to a React ref, the draggable
area was computed once from the initial render and never updated when the
draggable element (or constraints container) changed size afterwards. It is
the most-subscribed open drag issue (9 comments, workarounds via key
remounting). Since it was filed, main gained a ResizeObserver-driven
constraint recalculation (PR #3690, commits 801a699a5 + a4df97a6c,
regression test ef448a8d5 for #2903/#2458) and a root-scroll refresh before
ref-constraint measurement (cfccb0300, #2829). The core repro is almost
certainly fixed, but there is no regression test for the React-state-driven
resize variant that #1827 actually reported (existing fixtures resize via
motion values or imperative DOM mutation). This plan verifies, adds that
test, and closes the issue with an accurate breakdown of which commenter
variants are fixed and which belong to the scaled-drag cluster.
packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts
resolveConstraints() runs at every drag start (onStart calls it, line 136). For ref constraints it reuses the cache: lines 351–354:
if (dragConstraints && isRefObject(dragConstraints)) {
if (!this.constraints) {
this.constraints = this.resolveRefConstraints()
}
addListeners() (lines 699–728) installs a ResizeObserver on both the draggable element and the constraints element via startResizeObservers (lines 803–814); on resize it calls scalePositionWithinConstraints() (lines 589–659), which resets this.constraints = false and re-resolves with a fresh layout measurement.resolveRefConstraints() refreshes root scroll before measuring (lines 423–426, from cfccb0300).dev/react/src/tests/drag-ref-constraints-element-resize.tsx + packages/framer-motion/cypress/integration/drag-ref-constraints-element-resize.ts — draggable resized via motion values (issue #2458).dev/react/src/tests/drag-ref-constraints-resize-handle.tsx + spec — imperative DOM mutation resize (issue #2903).7ixkj0, and phn2mh from o-alexandrov's comment) is Cloudflare-blocked for agents; the issue body + comments fully describe the repro: a button changes the draggable's size after first render; the drag range must change accordingly.scale on the draggable or an ancestor (vavra7, kvnzrch) — transform changes do NOT fire ResizeObserver and raw CSS scale is invisible to projection; this is the scaled-drag cluster (see plans/issues/issue-2449.md); not fixed here, point them at correctParentTransform.dragConstraints switching false → {top: 200} (o-alexandrov) — plain-object constraints are recomputed at every drag start (resolveConstraints, lines 356–360), so this should already work; spot-check in Step 3.| Purpose | Command | Expected |
|---|---|---|
| Build (repo root) | yarn build | exit 0 |
| Dev server (React 18) | PORT=$((10000 + RANDOM % 50000)); cd dev/react && TEST_PORT=$PORT yarn vite --port $PORT & then npx wait-on http://localhost:$PORT | server up |
| Cypress (React 18) | cd packages/framer-motion && cypress run --headed --config baseUrl=http://localhost:$PORT --spec cypress/integration/<spec>.ts | all pass |
| Dev server (React 19) | same pattern from dev/react-19, fresh $PORT | server up |
| Cypress (React 19) | add --config-file=cypress.react-19.json | all pass |
Run Cypress in the foreground (never background). Capture output with tail -60.
In scope (the only files you may create/modify):
dev/react/src/tests/drag-ref-constraints-rerender-resize.tsx (create)packages/framer-motion/cypress/integration/drag-ref-constraints-rerender-resize.ts (create)plans/issues/README.md (your status row)Out of scope:
VisualElementDragControls.ts — this is a verification plan; if verification fails, STOP, do not patch.plans/issues/issue-2449.md / issue-2750.md / issue-1764.md.yarn build, start the React 18 server, then run:
cypress run --headed --config baseUrl=http://localhost:$PORT --spec "cypress/integration/drag-ref-constraints-element-resize.ts,cypress/integration/drag-ref-constraints-resize-handle.ts"
Verify: both specs pass. If either fails, STOP (main has regressed; the issue is not fixed).
Create dev/react/src/tests/drag-ref-constraints-rerender-resize.tsx exporting named App:
a 500×500 div with ref={constraintsRef} (position: relative), containing a
motion.div with drag, dragConstraints={constraintsRef}, dragElastic={0},
dragMomentum={false}, and width/height from useState (initially 100,
toggled to 300 by a <button id="resize-trigger"> that calls setState). The
size must flow through React state into the style prop (NOT motion values —
that's what distinguishes this from drag-ref-constraints-element-resize.tsx).
Model the markup and ids on dev/react/src/tests/drag-ref-constraints-element-resize.tsx.
Verify: page loads at http://localhost:$PORT/?test=drag-ref-constraints-rerender-resize (curl returns 200 or check via the Cypress run in Step 3).
Create packages/framer-motion/cypress/integration/drag-ref-constraints-rerender-resize.ts, modeled on drag-ref-constraints-element-resize.ts:
getBoundingClientRect().right clamps to ≤ ~500 (container edge), i.e. travel ≈ 400px.#resize-trigger, cy.wait(200) (let ResizeObserver + scalePositionWithinConstraints run).left can be at most ~200. Assert left ≤ 210 after an oversized drag. Without the ResizeObserver fix this fails (the old cached constraints allow 400px of travel → left ≈ 400)..then() for measurements where mid-state matters; allow a few px tolerance.Run on React 18, then repeat server+run on React 19 (cypress.react-19.json). Both MUST pass (CI runs both).
Verify: spec passes on React 18 AND React 19.
Gate: only execute this step if this plan's row in plans/issues/README.md is marked APPROVED by the maintainer.
Post a comment via gh api repos/motiondivision/motion/issues/1827/comments -f body="..." summarizing:
scale-related reports in this thread (vavra7, kvnzrch) are a different mechanism (transforms don't fire ResizeObserver; raw CSS scale is invisible to the projection system) — point to correctParentTransform + MotionConfig transformPagePoint and the scaled-drag tracking issue.Then close: gh api -X PATCH repos/motiondivision/motion/issues/1827 -f state=closed -f state_reason=completed.
(Do NOT use gh issue close/gh pr edit — Projects Classic GraphQL deprecation breaks some gh subcommands on this repo.)
drag-ref-constraints-rerender-resize (state-driven resize → constraints update). This is the regression gate for #1827's exact repro.drag-ref-constraints-element-resize, drag-ref-constraints-resize-handle must stay green.git status)plans/issues/README.md row updatedtail -60), do not attempt a fix under this plan.VisualElementDragControls.ts excerpts don't match (drift).plans/019-port-drag-pan-engine-to-motion-dom.md) lands first, the drag engine file moves to motion-dom; the line references here go stale but the behavior contract (ResizeObserver recalcs ref constraints) is what the new spec pins.