Skip to content

Commit 8973115

Browse files
authored
fix: Item position drift on slow web container resize (#562)
## Description On web, slowly resizing a Sortable container (e.g. dragging the docs split-pane divider) left grid items with wrong sizes/positions. Cause: `useItemLayout` only applied a new position when it differed by 1px+ from the previous reaction input, but that baseline advances on every run even when the update is skipped - so a slow resize's sub-pixel steps were skipped forever and positions drifted out of sync with the CSS-driven sizes. Fix: compare against the current position instead, and snap to the exact target on the non-animated path. ## Changes showcase Repro: Grid > Examples > Touchable, widen so the split shows, drag the divider slowly. Items now stay aligned (before: stale positions while shrinking).
1 parent e9b81e3 commit 8973115

1 file changed

Lines changed: 16 additions & 17 deletions

File tree

packages/react-native-sortables/src/providers/shared/hooks/useItemLayout.ts

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -170,38 +170,36 @@ export default function useItemLayout(
170170
useAnimatedReaction(
171171
() => ({
172172
active: isActive.value,
173-
itemPosition: layoutPosition.value,
173+
layoutPos: layoutPosition.value,
174174
progress: activationAnimationProgress.value
175175
}),
176-
({ active, itemPosition, progress }, prev) => {
177-
if (!itemPosition || active) {
176+
({ active, layoutPos, progress }, prev) => {
177+
if (!layoutPos || active) {
178178
interpolationStartValues.value = null;
179179
return;
180180
}
181181

182182
if (!position.value) {
183-
position.value = itemPosition;
183+
position.value = layoutPos;
184184
return;
185185
}
186186

187-
const positionChanged =
188-
prev?.itemPosition &&
189-
areVectorsDifferent(prev.itemPosition, itemPosition, 1);
190-
191187
if (progress === 0) {
192188
// interpolationStartValues value is not set when the reduced motion
193189
// is enabled as the progress value changes immediately from 1 to 0
194190
// and the second if branch below is never entered
195191
if (interpolationStartValues.value || prev?.active) {
196192
interpolationStartValues.value = null;
197-
position.value = itemPosition;
193+
position.value = layoutPos;
198194
return;
199195
}
200196
}
201197
// Set dropStartValues only if the item was previously active or if is
202198
// already during the drop animation and the target position changed
203199
else if (
204-
interpolationStartValues.value ? positionChanged : prev?.active
200+
interpolationStartValues.value
201+
? prev?.layoutPos && areVectorsDifferent(prev.layoutPos, layoutPos, 1)
202+
: prev?.active
205203
) {
206204
interpolationStartValues.value = {
207205
position: position.value,
@@ -220,22 +218,23 @@ export default function useItemLayout(
220218
position.value = interpolateVector(
221219
currentProgress,
222220
startPosition,
223-
itemPosition
221+
layoutPos
224222
);
225223
return;
226224
}
227225

228-
if (!positionChanged) {
229-
return;
230-
}
231-
232226
if (
233227
shouldAnimateLayout.value &&
234228
(!animateLayoutOnReorderOnly.value || activeItemKey.value !== null)
235229
) {
236-
position.value = withTiming(itemPosition);
230+
// Compare against the current position so sub-pixel changes accumulate
231+
// (the previous reaction input advances even when the update is skipped)
232+
if (!areVectorsDifferent(position.value, layoutPos, 1)) {
233+
return;
234+
}
235+
position.value = withTiming(layoutPos);
237236
} else {
238-
position.value = itemPosition;
237+
position.value = layoutPos;
239238
}
240239
}
241240
);

0 commit comments

Comments
 (0)