Skip to content

fix: autoAdjustOffsetDuringDrag stale offset and missing reorder - #606

Draft
MatiPl01 wants to merge 1 commit into
mainfrom
claude/react-native-sortables-605-df76f6
Draft

fix: autoAdjustOffsetDuringDrag stale offset and missing reorder#606
MatiPl01 wants to merge 1 commit into
mainfrom
claude/react-native-sortables-605-df76f6

Conversation

@MatiPl01

Copy link
Copy Markdown
Owner

Description

Fixes two bugs in Sortable.Grid's autoAdjustOffsetDuringDrag when items change size (collapsible cards), reported in #605.

Bug 1 - an at-rest size change after a drag permanently disables sorting. handleDragEnd leaves prevActiveItemKey set to the just-dropped key (cleared only at the next drag start). In adaptLayoutProps, itemKey = activeItemKey ?? prevActiveItemKey then resolves to that stale key on an at-rest "collapse/expand all", so the offset-application block runs and sets sortEnabled = false with no drop event to restore it. Fix: pass the props through unchanged when no item is being dragged.

Bug 2 - a drag with no mid-drag size change never reorders. othersLayout was gated to null while additionalCrossOffset was null, and that only becomes non-null once a mid-drag size change routes through the offset block, so a drag over uniform (already-collapsed) items never produced an order change. Fix: compute othersLayout unconditionally (calculateLayout already treats startCrossOffset as ?? 0) and hold ordering only during the mid-drag size transition, when comparing the finger's old-layout position against a new-sizes/offset-0 model would otherwise fire a far-jump reorder.

The drag-start cross sizes are snapshotted via a reaction on activeItemKey, re-captured on every drag start, so ordering keeps working even when the same item is dragged twice in a row (a plain per-drag flag would go stale after an at-rest re-measure between two same-key drags).

Testing

Reproduced and verified on the iOS simulator with a minimal collapsible-cards grid (the reporter's repro). Before/after, from app-side callbacks:

Scenario Before After
All-collapsed drag (bug 2) DRAG ACTIVATED, no ORDER CHANGE, drop reverts reorders correctly
Drag then "Expand all" then drag (bug 1) touch only, DRAG ACTIVATED never fires (sorting dead) drag activates and reorders
Expanded drag (collapse on drag start) (worked) reorders, no far jump
Same item dragged twice with a toggle between n/a reorders
Grid without autoAdjustOffsetDuringDrag (worked) unchanged

yarn lib typecheck, eslint, and yarn lib test (34/34) all pass.

Closes #605

Two bugs in Sortable.Grid autoAdjustOffsetDuringDrag with collapsible items:

- An at-rest size change (e.g. a collapse/expand toggle) after a drag
  resolved a stale prevActiveItemKey, applied a bogus offset and set
  sortEnabled=false with no drop event to restore it, permanently disabling
  sorting. Pass through when no item is active.
- A drag with no mid-drag size change never reordered because othersLayout
  was gated on additionalCrossOffset being non-null. Compute it
  unconditionally and hold ordering only during the mid-drag size
  transition, snapshotting the drag-start cross sizes via a reaction so
  consecutive same-key drags keep working.
@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
react-native-sortables-docs Ignored Ignored Jul 15, 2026 6:56pm

@MatiPl01 MatiPl01 self-assigned this Jul 15, 2026
@github-actions github-actions Bot added the fix label Jul 15, 2026

@lordpotato89 lordpotato89 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What matches our patch exactly
Bug 1 guard (AutoOffsetAdjustmentProvider.tsx): if (activeItemKey.value === null) return props; at the identical location (after the post-drop restore branch, before snapBasedOffset). Semantically character-for-character our fix — stops the at-rest "collapse all" from applying a stale-key offset and permanently killing sortEnabled.
Bug 2 null-gate removal (GridLayoutProvider/updates/common.ts): othersLayout computed unconditionally with startCrossOffset: additionalCrossOffset?.value ?? 0. Verified premise: calculateLayout already does startCrossOffset ?? 0 (layout.ts:29, 85). Vanilla grids (no auto-offset provider) are behaviorally unchanged (additionalCrossOffset === undefined short-circuits the new gate).
Transition-hold gate in the order updater: hold ordering only while additionalCrossOffset.value === null && dragStartSnapshot !== currentCrossSizes (reference equality) — exactly our round-8 companion fix that prevents the far-jump reorder during the mid-drag size flip.
The one difference — and it fixes a latent bug in OUR patch
Ours: snapshot taken lazily inside the updater via if (activeKey !== lastActiveKey.value). lastActiveKey never resets between drags, so dragging the same card twice in a row with an at-rest re-measure between (e.g. expand-all → collapse-all toggle) never re-snapshots → stale object → gate stays closed → if no mid-drag size change applies the offset, no reorder for the entire second drag (bug 2 resurfaces for that sequence). Reachable in our app when all cards are already collapsed.
PR: snapshot captured by a useAnimatedReaction on activeItemKey. Verified in source that activeItemKey cycles key → null → key on every drag (DragProvider.ts:531–537, handleDragEnd nulls it, prevActiveItemKey keeps the stale copy) — so the reaction re-fires on every drag start, same-key or not. The PR body explicitly calls this scenario out and its test matrix covers it ("Same item dragged twice with a toggle between").

@lordpotato89

Copy link
Copy Markdown

Are you merging your solution? Regards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

autoAdjustOffsetDuringDrag: stale offset on at-rest size changes disables sorting; ordering never fires for drags without a mid-drag size change

2 participants