react-native-draggable-flatlist
Version:
A drag-and-drop-enabled FlatList component for React Native
273 lines (272 loc) • 16.1 kB
JavaScript
;
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var react_1 = __importStar(require("react"));
var react_native_gesture_handler_1 = require("react-native-gesture-handler");
var react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
var CellRendererComponent_1 = __importDefault(require("./CellRendererComponent"));
var constants_1 = require("../constants");
var PlaceholderItem_1 = __importDefault(require("./PlaceholderItem"));
var RowItem_1 = __importDefault(require("./RowItem"));
var ScrollOffsetListener_1 = __importDefault(require("./ScrollOffsetListener"));
var useAutoScroll_1 = require("../hooks/useAutoScroll");
var useNode_1 = require("../hooks/useNode");
var propsContext_1 = __importDefault(require("../context/propsContext"));
var animatedValueContext_1 = __importStar(require("../context/animatedValueContext"));
var refContext_1 = __importStar(require("../context/refContext"));
var draggableFlatListContext_1 = __importDefault(require("../context/draggableFlatListContext"));
var AnimatedFlatList = react_native_reanimated_1.default.createAnimatedComponent(react_native_gesture_handler_1.FlatList);
function DraggableFlatListInner(props) {
var _a = refContext_1.useRefs(), cellDataRef = _a.cellDataRef, containerRef = _a.containerRef, flatListRef = _a.flatListRef, isTouchActiveRef = _a.isTouchActiveRef, keyToIndexRef = _a.keyToIndexRef, panGestureHandlerRef = _a.panGestureHandlerRef, propsRef = _a.propsRef, scrollOffsetRef = _a.scrollOffsetRef;
var _b = animatedValueContext_1.useAnimatedValues(), activationDistance = _b.activationDistance, activeCellOffset = _b.activeCellOffset, activeCellSize = _b.activeCellSize, activeIndexAnim = _b.activeIndexAnim, containerSize = _b.containerSize, disabled = _b.disabled, hasMoved = _b.hasMoved, panGestureState = _b.panGestureState, resetTouchedCell = _b.resetTouchedCell, scrollOffset = _b.scrollOffset, scrollViewSize = _b.scrollViewSize, spacerIndexAnim = _b.spacerIndexAnim, touchAbsolute = _b.touchAbsolute, touchInit = _b.touchInit;
var _c = props.dragHitSlop, dragHitSlop = _c === void 0 ? constants_1.DEFAULT_PROPS.dragHitSlop : _c, _d = props.scrollEnabled, scrollEnabled = _d === void 0 ? constants_1.DEFAULT_PROPS.scrollEnabled : _d, _e = props.activationDistance, activationDistanceProp = _e === void 0 ? constants_1.DEFAULT_PROPS.activationDistance : _e;
var _f = react_1.useState(null), activeKey = _f[0], setActiveKey = _f[1];
var keyExtractor = react_1.useCallback(function (item, index) {
if (propsRef.current.keyExtractor)
return propsRef.current.keyExtractor(item, index);
else
throw new Error("You must provide a keyExtractor to DraggableFlatList");
}, [propsRef]);
react_1.useLayoutEffect(function () {
props.data.forEach(function (d, i) {
var key = keyExtractor(d, i);
keyToIndexRef.current.set(key, i);
});
}, [props.data, keyExtractor, keyToIndexRef]);
var drag = react_1.useCallback(function (activeKey) {
if (!isTouchActiveRef.current.js)
return;
var index = keyToIndexRef.current.get(activeKey);
var cellData = cellDataRef.current.get(activeKey);
if (cellData) {
activeCellOffset.setValue(cellData.measurements.offset - scrollOffsetRef.current);
activeCellSize.setValue(cellData.measurements.size);
}
var onDragBegin = propsRef.current.onDragBegin;
if (index !== undefined) {
spacerIndexAnim.setValue(index);
activeIndexAnim.setValue(index);
setActiveKey(activeKey);
onDragBegin === null || onDragBegin === void 0 ? void 0 : onDragBegin(index);
}
}, [
isTouchActiveRef,
keyToIndexRef,
cellDataRef,
propsRef,
activeCellOffset,
scrollOffsetRef,
activeCellSize,
spacerIndexAnim,
activeIndexAnim,
]);
var autoScrollNode = useAutoScroll_1.useAutoScroll();
var onContainerLayout = function (_a) {
var layout = _a.nativeEvent.layout;
containerSize.setValue(props.horizontal ? layout.width : layout.height);
};
var onListContentSizeChange = function (w, h) {
var _a;
scrollViewSize.setValue(props.horizontal ? w : h);
(_a = props.onContentSizeChange) === null || _a === void 0 ? void 0 : _a.call(props, w, h);
};
var onContainerTouchStart = function () {
isTouchActiveRef.current.js = true;
isTouchActiveRef.current.native.setValue(1);
return false;
};
var onContainerTouchEnd = function () {
isTouchActiveRef.current.js = false;
isTouchActiveRef.current.native.setValue(0);
};
var dynamicProps = {};
if (activationDistanceProp) {
var activeOffset = [-activationDistanceProp, activationDistanceProp];
dynamicProps = props.horizontal
? { activeOffsetX: activeOffset }
: { activeOffsetY: activeOffset };
}
var extraData = react_1.useMemo(function () { return ({
activeKey: activeKey,
extraData: props.extraData,
}); }, [activeKey, props.extraData]);
var renderItem = react_1.useCallback(function (_a) {
var item = _a.item, index = _a.index;
var key = keyExtractor(item, index);
if (index !== keyToIndexRef.current.get(key))
keyToIndexRef.current.set(key, index);
return (react_1.default.createElement(RowItem_1.default, { item: item, itemKey: key, renderItem: props.renderItem, drag: drag, extraData: props.extraData }));
}, [props.renderItem, props.extraData, drag, keyExtractor]);
var resetHoverState = react_1.useCallback(function () {
activeIndexAnim.setValue(-1);
spacerIndexAnim.setValue(-1);
touchAbsolute.setValue(0);
disabled.setValue(0);
requestAnimationFrame(function () {
setActiveKey(null);
});
}, [activeIndexAnim, spacerIndexAnim, touchAbsolute, disabled]);
var onRelease = function (_a) {
var _b;
var index = _a[0];
// This shouldn't be necessary but seems to fix a bug where sometimes
// native values wouldn't update
isTouchActiveRef.current.native.setValue(0);
(_b = props.onRelease) === null || _b === void 0 ? void 0 : _b.call(props, index);
};
var onDragEnd = react_1.useCallback(function (_a) {
var from = _a[0], to = _a[1];
var _b = propsRef.current, onDragEnd = _b.onDragEnd, data = _b.data;
if (onDragEnd) {
var newData = __spreadArray([], data);
if (from !== to) {
newData.splice(from, 1);
newData.splice(to, 0, data[from]);
}
onDragEnd({ from: from, to: to, data: newData });
}
resetHoverState();
}, [resetHoverState, propsRef]);
var onGestureRelease = useNode_1.useNode(react_native_reanimated_1.cond(react_native_reanimated_1.greaterThan(activeIndexAnim, -1), [
react_native_reanimated_1.set(disabled, 1),
react_native_reanimated_1.set(isTouchActiveRef.current.native, 0),
react_native_reanimated_1.call([activeIndexAnim], onRelease),
react_native_reanimated_1.cond(react_native_reanimated_1.not(hasMoved), react_native_reanimated_1.call([activeIndexAnim], resetHoverState)),
], [react_native_reanimated_1.call([activeIndexAnim], resetHoverState), resetTouchedCell]));
var onPanStateChange = react_1.useMemo(function () {
return react_native_reanimated_1.event([
{
nativeEvent: function (_a) {
var state = _a.state, x = _a.x, y = _a.y;
return react_native_reanimated_1.block([
react_native_reanimated_1.cond(react_native_reanimated_1.and(react_native_reanimated_1.neq(state, panGestureState), react_native_reanimated_1.not(disabled)), [
react_native_reanimated_1.cond(react_native_reanimated_1.or(react_native_reanimated_1.eq(state, react_native_gesture_handler_1.State.BEGAN), // Called on press in on Android, NOT on ios!
// GestureState.BEGAN may be skipped on fast swipes
react_native_reanimated_1.and(react_native_reanimated_1.eq(state, react_native_gesture_handler_1.State.ACTIVE), react_native_reanimated_1.neq(panGestureState, react_native_gesture_handler_1.State.BEGAN))), [
react_native_reanimated_1.set(touchAbsolute, props.horizontal ? x : y),
react_native_reanimated_1.set(touchInit, touchAbsolute),
]),
react_native_reanimated_1.cond(react_native_reanimated_1.eq(state, react_native_gesture_handler_1.State.ACTIVE), [
react_native_reanimated_1.set(activationDistance, react_native_reanimated_1.sub(props.horizontal ? x : y, touchInit)),
react_native_reanimated_1.set(touchAbsolute, props.horizontal ? x : y),
]),
]),
react_native_reanimated_1.cond(react_native_reanimated_1.neq(panGestureState, state), [
react_native_reanimated_1.set(panGestureState, state),
react_native_reanimated_1.cond(react_native_reanimated_1.or(react_native_reanimated_1.eq(state, react_native_gesture_handler_1.State.END), react_native_reanimated_1.eq(state, react_native_gesture_handler_1.State.CANCELLED), react_native_reanimated_1.eq(state, react_native_gesture_handler_1.State.FAILED)), onGestureRelease),
]),
]);
},
},
]);
}, [
activationDistance,
props.horizontal,
panGestureState,
disabled,
onGestureRelease,
touchAbsolute,
touchInit,
]);
var onPanGestureEvent = react_1.useMemo(function () {
return react_native_reanimated_1.event([
{
nativeEvent: function (_a) {
var x = _a.x, y = _a.y;
return react_native_reanimated_1.cond(react_native_reanimated_1.and(react_native_reanimated_1.greaterThan(activeIndexAnim, -1), react_native_reanimated_1.eq(panGestureState, react_native_gesture_handler_1.State.ACTIVE), react_native_reanimated_1.not(disabled)), [
react_native_reanimated_1.cond(react_native_reanimated_1.not(hasMoved), react_native_reanimated_1.set(hasMoved, 1)),
react_native_reanimated_1.set(touchAbsolute, props.horizontal ? x : y),
]);
},
},
]);
}, [
activeIndexAnim,
disabled,
hasMoved,
panGestureState,
props.horizontal,
touchAbsolute,
]);
var scrollHandler = react_1.useMemo(function () {
// Web doesn't seem to like animated events
var webOnScroll = function (_a) {
var _b = _a.nativeEvent.contentOffset, x = _b.x, y = _b.y;
scrollOffset.setValue(props.horizontal ? x : y);
};
var mobileOnScroll = react_native_reanimated_1.event([
{
nativeEvent: function (_a) {
var contentOffset = _a.contentOffset;
return react_native_reanimated_1.block([
react_native_reanimated_1.set(scrollOffset, props.horizontal ? contentOffset.x : contentOffset.y),
autoScrollNode,
]);
},
},
]);
return constants_1.isWeb ? webOnScroll : mobileOnScroll;
}, [autoScrollNode, props.horizontal, scrollOffset]);
return (react_1.default.createElement(draggableFlatListContext_1.default, { activeKey: activeKey, onDragEnd: onDragEnd, keyExtractor: keyExtractor, horizontal: !!props.horizontal },
react_1.default.createElement(react_native_gesture_handler_1.PanGestureHandler, __assign({ ref: panGestureHandlerRef, hitSlop: dragHitSlop, onHandlerStateChange: onPanStateChange, onGestureEvent: onPanGestureEvent, simultaneousHandlers: props.simultaneousHandlers }, dynamicProps),
react_1.default.createElement(react_native_reanimated_1.default.View, { style: props.containerStyle, ref: containerRef, onLayout: onContainerLayout, onTouchEnd: onContainerTouchEnd, onStartShouldSetResponderCapture: onContainerTouchStart,
//@ts-ignore
onClick: onContainerTouchEnd },
react_1.default.createElement(ScrollOffsetListener_1.default, { scrollOffset: scrollOffset, onScrollOffsetChange: function (_a) {
var _b;
var offset = _a[0];
scrollOffsetRef.current = offset;
(_b = props.onScrollOffsetChange) === null || _b === void 0 ? void 0 : _b.call(props, offset);
} }),
react_1.default.createElement(PlaceholderItem_1.default, { renderPlaceholder: props.renderPlaceholder }),
react_1.default.createElement(AnimatedFlatList, __assign({}, props, { CellRendererComponent: CellRendererComponent_1.default, ref: flatListRef, onContentSizeChange: onListContentSizeChange, scrollEnabled: !activeKey && scrollEnabled, renderItem: renderItem, extraData: extraData, keyExtractor: keyExtractor, onScroll: scrollHandler, scrollEventThrottle: 16, simultaneousHandlers: props.simultaneousHandlers, removeClippedSubviews: false })),
react_1.default.createElement(react_native_reanimated_1.default.Code, { dependencies: [] }, function () {
return react_native_reanimated_1.block([
react_native_reanimated_1.onChange(isTouchActiveRef.current.native, react_native_reanimated_1.cond(react_native_reanimated_1.not(isTouchActiveRef.current.native), onGestureRelease)),
]);
})))));
}
function DraggableFlatList(props, ref) {
return (react_1.default.createElement(propsContext_1.default, __assign({}, props),
react_1.default.createElement(animatedValueContext_1.default, null,
react_1.default.createElement(refContext_1.default, { flatListRef: ref },
react_1.default.createElement(DraggableFlatListInner, __assign({}, props))))));
}
exports.default = react_1.default.forwardRef(DraggableFlatList);