Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,15 @@ const { AutoOffsetAdjustmentProvider, useAutoOffsetAdjustmentContext } =
};
}

// At-rest cross-size change (e.g. a "collapse all"/"expand all" toggle)
// with no active drag: `itemKey` above fell back to a stale
// `prevActiveItemKey` from the previous drag. Applying an offset here
// would shift the whole layout and set `sortEnabled = false` with no
// drop event ever coming to restore it, permanently disabling sorting.
if (activeItemKey.value === null) {
return props;
}

let snapBasedOffset = 0;

if (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { type SharedValue, useDerivedValue } from 'react-native-reanimated';
import {
type SharedValue,
useAnimatedReaction,
useDerivedValue
} from 'react-native-reanimated';

import { useMutableValue } from '../../../../integrations/reanimated';
import type {
Coordinate,
Dimension,
ItemSizes,
ReorderFunction,
SortStrategyFactory
} from '../../../../types';
Expand All @@ -23,6 +29,7 @@ export const createGridStrategy =
): SortStrategyFactory =>
() => {
const {
activeItemKey,
containerHeight,
containerWidth,
indexToKey,
Expand All @@ -37,21 +44,37 @@ export const createGridStrategy =
const othersIndexToKey = useInactiveIndexToKey();
const debugBox = useDebugBoundingBox();

// Cross sizes captured at each drag start. Used to detect a mid-drag size
// change (e.g. items collapsing on drag start) that is still in flight
// before the auto offset is applied - see the gate in the order updater.
// Re-captured on every drag start (activeItemKey null -> key), so it stays
// correct even when the same item is dragged twice in a row.
const dragStartCrossSizes = useMutableValue<ItemSizes>(null);

useAnimatedReaction(
() => activeItemKey.value,
key => {
if (key !== null) {
dragStartCrossSizes.value = isVertical
? itemHeights.value
: itemWidths.value;
}
}
);

const othersLayout = useDerivedValue(() =>
additionalCrossOffset?.value === null
? null
: calculateLayout({
gaps: {
cross: crossGap.value,
main: mainGap.value
},
indexToKey: othersIndexToKey.value,
isVertical,
itemHeights: itemHeights.value,
itemWidths: itemWidths.value,
numGroups,
startCrossOffset: additionalCrossOffset?.value
})
calculateLayout({
gaps: {
cross: crossGap.value,
main: mainGap.value
},
indexToKey: othersIndexToKey.value,
isVertical,
itemHeights: itemHeights.value,
itemWidths: itemWidths.value,
numGroups,
startCrossOffset: additionalCrossOffset?.value ?? 0
})
);

let mainContainerSize: SharedValue<null | number>;
Expand All @@ -76,6 +99,26 @@ export const createGridStrategy =

return ({ activeIndex, dimensions, position }) => {
'worklet';
// While the auto offset is enabled (additionalCrossOffset defined) but not
// yet applied (value still null), hold ordering only during a mid-drag
// size transition - when the cross sizes changed since the drag started.
// In that window othersLayout (new sizes, offset 0) is inconsistent with
// the on-screen layout and the finger position, so the bounds walk would
// fire a far-jump reorder that the offset block then anchors on. Ordering
// resumes as soon as the offset is applied. Drags with no size change
// (uniform or already-collapsed items) are unaffected - the snapshot
// stays reference-equal, so ordering works from the first move.
const currentCrossSizes = isVertical
? itemHeights.value
: itemWidths.value;
if (
additionalCrossOffset &&
additionalCrossOffset.value === null &&
dragStartCrossSizes.value !== currentCrossSizes
) {
return;
}

if (
!othersLayout.value ||
crossContainerSize.value === null ||
Expand Down
Loading