diff --git a/build/DraxContext.d.ts b/build/DraxContext.d.ts
new file mode 100644
index 0000000..b952f40
--- /dev/null
+++ b/build/DraxContext.d.ts
@@ -0,0 +1,3 @@
+///
+import { DraxContextValue } from './types';
+export declare const DraxContext: import("react").Context;
diff --git a/build/DraxContext.js b/build/DraxContext.js
new file mode 100644
index 0000000..7c35fea
--- /dev/null
+++ b/build/DraxContext.js
@@ -0,0 +1,6 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxContext = void 0;
+const react_1 = require("react");
+exports.DraxContext = (0, react_1.createContext)(undefined);
+exports.DraxContext.displayName = 'Drax';
diff --git a/build/DraxList.d.ts b/build/DraxList.d.ts
new file mode 100644
index 0000000..53b0b07
--- /dev/null
+++ b/build/DraxList.d.ts
@@ -0,0 +1,8 @@
+import { PropsWithChildren, Ref } from 'react';
+import { FlatList } from 'react-native';
+import { DraxListProps } from './types';
+declare type DraxListType = (props: PropsWithChildren> & {
+ ref?: Ref;
+}) => JSX.Element;
+export declare const DraxList: DraxListType;
+export {};
diff --git a/build/DraxList.js b/build/DraxList.js
new file mode 100644
index 0000000..456c190
--- /dev/null
+++ b/build/DraxList.js
@@ -0,0 +1,497 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (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;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxList = void 0;
+const react_1 = __importStar(require("react"));
+const react_native_1 = require("react-native");
+const DraxView_1 = require("./DraxView");
+const DraxSubprovider_1 = require("./DraxSubprovider");
+const hooks_1 = require("./hooks");
+const types_1 = require("./types");
+const params_1 = require("./params");
+const defaultStyles = react_native_1.StyleSheet.create({
+ draggingStyle: { opacity: 0 },
+ dragReleasedStyle: { opacity: 0.5 },
+});
+const DraxListUnforwarded = (props, forwardedRef) => {
+ const { data, style, flatListStyle, itemStyles, renderItemContent, renderItemHoverContent, onItemDragStart, onItemDragPositionChange, onItemDragEnd, onItemReorder, viewPropsExtractor, id: idProp, reorderable: reorderableProp, onScroll: onScrollProp, itemsDraggable = true, lockItemDragsToMainAxis = false, longPressDelay = params_1.defaultListItemLongPressDelay, ...flatListProps } = props;
+ // Copy the value of the horizontal property for internal use.
+ const horizontal = flatListProps.horizontal ?? false;
+ // Get the item count for internal use.
+ const itemCount = data?.length ?? 0;
+ // Set a sensible default for reorderable prop.
+ const reorderable = reorderableProp ?? (onItemReorder !== undefined);
+ // The unique identifer for this list's Drax view.
+ const id = (0, hooks_1.useDraxId)(idProp);
+ // FlatList, used for scrolling.
+ const flatListRef = (0, react_1.useRef)(null);
+ // FlatList node handle, used for measuring children.
+ const nodeHandleRef = (0, react_1.useRef)(null);
+ // Container view measurements, for scrolling by percentage.
+ const containerMeasurementsRef = (0, react_1.useRef)(undefined);
+ // Content size, for scrolling by percentage.
+ const contentSizeRef = (0, react_1.useRef)(undefined);
+ // Scroll position, for Drax bounds checking and auto-scrolling.
+ const scrollPositionRef = (0, react_1.useRef)({ x: 0, y: 0 });
+ // Original index of the currently dragged list item, if any.
+ const draggedItemRef = (0, react_1.useRef)(undefined);
+ // Auto-scrolling state.
+ const scrollStateRef = (0, react_1.useRef)(types_1.AutoScrollDirection.None);
+ // Auto-scrolling interval.
+ const scrollIntervalRef = (0, react_1.useRef)(undefined);
+ // List item measurements, for determining shift.
+ const itemMeasurementsRef = (0, react_1.useRef)([]);
+ // Drax view registrations, for remeasuring after reorder.
+ const registrationsRef = (0, react_1.useRef)([]);
+ // Shift offsets.
+ const shiftsRef = (0, react_1.useRef)([]);
+ // Maintain cache of reordered list indexes until data updates.
+ const [originalIndexes, setOriginalIndexes] = (0, react_1.useState)([]);
+ // Maintain the index the item is currently dragged to.
+ const draggedToIndex = (0, react_1.useRef)(undefined);
+ // Adjust measurements, registrations, and shift value arrays as item count changes.
+ (0, react_1.useEffect)(() => {
+ const itemMeasurements = itemMeasurementsRef.current;
+ const registrations = registrationsRef.current;
+ const shifts = shiftsRef.current;
+ if (itemMeasurements.length > itemCount) {
+ itemMeasurements.splice(itemCount - itemMeasurements.length);
+ }
+ else {
+ while (itemMeasurements.length < itemCount) {
+ itemMeasurements.push(undefined);
+ }
+ }
+ if (registrations.length > itemCount) {
+ registrations.splice(itemCount - registrations.length);
+ }
+ else {
+ while (registrations.length < itemCount) {
+ registrations.push(undefined);
+ }
+ }
+ if (shifts.length > itemCount) {
+ shifts.splice(itemCount - shifts.length);
+ }
+ else {
+ while (shifts.length < itemCount) {
+ shifts.push({
+ targetValue: 0,
+ animatedValue: new react_native_1.Animated.Value(0),
+ });
+ }
+ }
+ }, [itemCount]);
+ // Clear reorders when data changes.
+ (0, react_1.useLayoutEffect)(() => {
+ // console.log('clear reorders');
+ setOriginalIndexes(data ? [...Array(data.length).keys()] : []);
+ }, [data]);
+ // Apply the reorder cache to the data.
+ const reorderedData = (0, react_1.useMemo)(() => {
+ // console.log('refresh sorted data');
+ if (!id || !data) {
+ return null;
+ }
+ if (data.length !== originalIndexes.length) {
+ return data;
+ }
+ return originalIndexes.map((index) => data[index]);
+ }, [id, data, originalIndexes]);
+ // Get shift transform for list item at index.
+ const getShiftTransform = (0, react_1.useCallback)((index) => {
+ const shift = shiftsRef.current[index]?.animatedValue ?? 0;
+ return horizontal
+ ? [{ translateX: shift }]
+ : [{ translateY: shift }];
+ }, [horizontal]);
+ // Set the currently dragged list item.
+ const setDraggedItem = (0, react_1.useCallback)((originalIndex) => {
+ draggedItemRef.current = originalIndex;
+ }, []);
+ // Clear the currently dragged list item.
+ const resetDraggedItem = (0, react_1.useCallback)(() => {
+ draggedItemRef.current = undefined;
+ }, []);
+ // Drax view renderItem wrapper.
+ const renderItem = (0, react_1.useCallback)((info) => {
+ const { index, item } = info;
+ const originalIndex = originalIndexes[index];
+ const { style: itemStyle, draggingStyle = defaultStyles.draggingStyle, dragReleasedStyle = defaultStyles.dragReleasedStyle, ...otherStyleProps } = itemStyles ?? {};
+ return (react_1.default.createElement(DraxView_1.DraxView, { style: [itemStyle, { transform: getShiftTransform(originalIndex) }], draggingStyle: draggingStyle, dragReleasedStyle: dragReleasedStyle, ...otherStyleProps, longPressDelay: longPressDelay, lockDragXPosition: lockItemDragsToMainAxis && !horizontal, lockDragYPosition: lockItemDragsToMainAxis && horizontal, draggable: itemsDraggable, payload: { index, originalIndex }, ...(viewPropsExtractor?.(item) ?? {}), onDragEnd: resetDraggedItem, onDragDrop: resetDraggedItem, onMeasure: (measurements) => {
+ if (originalIndex !== undefined) {
+ // console.log(`measuring [${index}, ${originalIndex}]: (${measurements?.x}, ${measurements?.y})`);
+ itemMeasurementsRef.current[originalIndex] = measurements;
+ }
+ }, registration: (registration) => {
+ if (registration && originalIndex !== undefined) {
+ // console.log(`registering [${index}, ${originalIndex}], ${registration.id}`);
+ registrationsRef.current[originalIndex] = registration;
+ registration.measure();
+ }
+ }, renderContent: (contentProps) => renderItemContent(info, contentProps), renderHoverContent: renderItemHoverContent
+ && ((hoverContentProps) => renderItemHoverContent(info, hoverContentProps)) }));
+ }, [
+ originalIndexes,
+ itemStyles,
+ viewPropsExtractor,
+ getShiftTransform,
+ resetDraggedItem,
+ itemsDraggable,
+ renderItemContent,
+ renderItemHoverContent,
+ longPressDelay,
+ lockItemDragsToMainAxis,
+ horizontal,
+ ]);
+ // Track the size of the container view.
+ const onMeasureContainer = (0, react_1.useCallback)((measurements) => {
+ containerMeasurementsRef.current = measurements;
+ }, []);
+ // Track the size of the content.
+ const onContentSizeChange = (0, react_1.useCallback)((width, height) => {
+ contentSizeRef.current = { x: width, y: height };
+ }, []);
+ // Set FlatList and node handle refs.
+ const setFlatListRefs = (0, react_1.useCallback)((ref) => {
+ flatListRef.current = ref;
+ nodeHandleRef.current = ref && (0, react_native_1.findNodeHandle)(ref);
+ if (forwardedRef) {
+ if (typeof forwardedRef === 'function') {
+ forwardedRef(ref);
+ }
+ else {
+ // eslint-disable-next-line no-param-reassign
+ forwardedRef.current = ref;
+ }
+ }
+ }, [forwardedRef]);
+ // Update tracked scroll position when list is scrolled.
+ const onScroll = (0, react_1.useCallback)((event) => {
+ const { nativeEvent: { contentOffset } } = event;
+ scrollPositionRef.current = { ...contentOffset };
+ onScrollProp?.(event);
+ }, [onScrollProp]);
+ // Handle auto-scrolling on interval.
+ const doScroll = (0, react_1.useCallback)(() => {
+ const flatList = flatListRef.current;
+ const containerMeasurements = containerMeasurementsRef.current;
+ const contentSize = contentSizeRef.current;
+ if (!flatList || !containerMeasurements || !contentSize) {
+ return;
+ }
+ let containerLength;
+ let contentLength;
+ let prevOffset;
+ if (horizontal) {
+ containerLength = containerMeasurements.width;
+ contentLength = contentSize.x;
+ prevOffset = scrollPositionRef.current.x;
+ }
+ else {
+ containerLength = containerMeasurements.height;
+ contentLength = contentSize.y;
+ prevOffset = scrollPositionRef.current.y;
+ }
+ const jumpLength = containerLength * 0.2;
+ let offset;
+ if (scrollStateRef.current === types_1.AutoScrollDirection.Forward) {
+ const maxOffset = contentLength - containerLength;
+ if (prevOffset < maxOffset) {
+ offset = Math.min(prevOffset + jumpLength, maxOffset);
+ }
+ }
+ else if (scrollStateRef.current === types_1.AutoScrollDirection.Back) {
+ if (prevOffset > 0) {
+ offset = Math.max(prevOffset - jumpLength, 0);
+ }
+ }
+ if (offset !== undefined) {
+ flatList.scrollToOffset({ offset });
+ flatList.flashScrollIndicators();
+ }
+ }, [horizontal]);
+ // Start the auto-scrolling interval.
+ const startScroll = (0, react_1.useCallback)(() => {
+ if (scrollIntervalRef.current) {
+ return;
+ }
+ doScroll();
+ scrollIntervalRef.current = setInterval(doScroll, 250);
+ }, [doScroll]);
+ // Stop the auto-scrolling interval.
+ const stopScroll = (0, react_1.useCallback)(() => {
+ if (scrollIntervalRef.current) {
+ clearInterval(scrollIntervalRef.current);
+ scrollIntervalRef.current = undefined;
+ }
+ }, []);
+ // If startScroll changes, refresh our interval.
+ (0, react_1.useEffect)(() => {
+ if (scrollIntervalRef.current) {
+ stopScroll();
+ startScroll();
+ }
+ }, [stopScroll, startScroll]);
+ // Reset all shift values.
+ const resetShifts = (0, react_1.useCallback)(() => {
+ shiftsRef.current.forEach((shift) => {
+ // eslint-disable-next-line no-param-reassign
+ shift.targetValue = 0;
+ shift.animatedValue.setValue(0);
+ });
+ }, []);
+ // Update shift values in response to a drag.
+ const updateShifts = (0, react_1.useCallback)(({ index: fromIndex, originalIndex: fromOriginalIndex }, { index: toIndex }) => {
+ const { width = 50, height = 50 } = itemMeasurementsRef.current[fromOriginalIndex] ?? {};
+ const offset = horizontal ? width : height;
+ originalIndexes.forEach((originalIndex, index) => {
+ const shift = shiftsRef.current[originalIndex];
+ let newTargetValue = 0;
+ if (index > fromIndex && index <= toIndex) {
+ newTargetValue = -offset;
+ }
+ else if (index < fromIndex && index >= toIndex) {
+ newTargetValue = offset;
+ }
+ if (shift.targetValue !== newTargetValue) {
+ shift.targetValue = newTargetValue;
+ react_native_1.Animated.timing(shift.animatedValue, {
+ duration: 200,
+ toValue: newTargetValue,
+ useNativeDriver: true,
+ }).start();
+ }
+ });
+ }, [originalIndexes, horizontal]);
+ // Calculate absolute position of list item for snapback.
+ const calculateSnapbackTarget = (0, react_1.useCallback)(({ index: fromIndex, originalIndex: fromOriginalIndex }, { index: toIndex, originalIndex: toOriginalIndex }) => {
+ const containerMeasurements = containerMeasurementsRef.current;
+ const itemMeasurements = itemMeasurementsRef.current;
+ if (containerMeasurements) {
+ let targetPos;
+ if (fromIndex < toIndex) {
+ // Target pos(toIndex + 1) - pos(fromIndex)
+ const nextIndex = toIndex + 1;
+ let nextPos;
+ if (nextIndex < itemCount) {
+ // toIndex + 1 is in the list. We can measure the position of the next item.
+ const nextMeasurements = itemMeasurements[originalIndexes[nextIndex]];
+ if (nextMeasurements) {
+ nextPos = { x: nextMeasurements.x, y: nextMeasurements.y };
+ }
+ }
+ else {
+ // toIndex is the last item of the list. We can use the list content size.
+ const contentSize = contentSizeRef.current;
+ if (contentSize) {
+ nextPos = horizontal
+ ? { x: contentSize.x, y: 0 }
+ : { x: 0, y: contentSize.y };
+ }
+ }
+ const fromMeasurements = itemMeasurements[fromOriginalIndex];
+ if (nextPos && fromMeasurements) {
+ targetPos = horizontal
+ ? { x: nextPos.x - fromMeasurements.width, y: nextPos.y }
+ : { x: nextPos.x, y: nextPos.y - fromMeasurements.height };
+ }
+ }
+ else {
+ // Target pos(toIndex)
+ const toMeasurements = itemMeasurements[toOriginalIndex];
+ if (toMeasurements) {
+ targetPos = { x: toMeasurements.x, y: toMeasurements.y };
+ }
+ }
+ if (targetPos) {
+ const scrollPosition = scrollPositionRef.current;
+ return {
+ x: containerMeasurements.x - scrollPosition.x + targetPos.x,
+ y: containerMeasurements.y - scrollPosition.y + targetPos.y,
+ };
+ }
+ }
+ return types_1.DraxSnapbackTargetPreset.None;
+ }, [horizontal, itemCount, originalIndexes]);
+ // Stop auto-scrolling, and potentially update shifts and reorder data.
+ const handleInternalDragEnd = (0, react_1.useCallback)((eventData, totalDragEnd) => {
+ // Always stop auto-scroll on drag end.
+ scrollStateRef.current = types_1.AutoScrollDirection.None;
+ stopScroll();
+ const { dragged, receiver } = eventData;
+ // Check if we need to handle this drag end.
+ if (reorderable && dragged.parentId === id) {
+ // Determine list indexes of dragged/received items, if any.
+ const fromPayload = dragged.payload;
+ const toPayload = receiver?.parentId === id
+ ? receiver.payload
+ : undefined;
+ const { index: fromIndex, originalIndex: fromOriginalIndex } = fromPayload;
+ const { index: toIndex, originalIndex: toOriginalIndex } = toPayload ?? {};
+ const toItem = (toOriginalIndex !== undefined) ? data?.[toOriginalIndex] : undefined;
+ // Reset all shifts and call callback, regardless of whether toPayload exists.
+ resetShifts();
+ if (totalDragEnd) {
+ onItemDragEnd?.({
+ ...eventData,
+ toIndex,
+ toItem,
+ cancelled: (0, types_1.isWithCancelledFlag)(eventData) ? eventData.cancelled : false,
+ index: fromIndex,
+ item: data?.[fromOriginalIndex],
+ });
+ }
+ // Reset currently dragged over position index to undefined.
+ if (draggedToIndex.current !== undefined) {
+ if (!totalDragEnd) {
+ onItemDragPositionChange?.({
+ ...eventData,
+ index: fromIndex,
+ item: data?.[fromOriginalIndex],
+ toIndex: undefined,
+ previousIndex: draggedToIndex.current,
+ });
+ }
+ draggedToIndex.current = undefined;
+ }
+ if (toPayload !== undefined) {
+ // If dragged item and received item were ours, reorder data.
+ // console.log(`moving ${fromPayload.index} -> ${toPayload.index}`);
+ const snapbackTarget = calculateSnapbackTarget(fromPayload, toPayload);
+ if (data) {
+ const newOriginalIndexes = originalIndexes.slice();
+ newOriginalIndexes.splice(toIndex, 0, newOriginalIndexes.splice(fromIndex, 1)[0]);
+ setOriginalIndexes(newOriginalIndexes);
+ onItemReorder?.({
+ fromIndex,
+ fromItem: data[fromOriginalIndex],
+ toIndex: toIndex,
+ toItem: data[toOriginalIndex],
+ });
+ }
+ return snapbackTarget;
+ }
+ }
+ return undefined;
+ }, [
+ id,
+ data,
+ stopScroll,
+ reorderable,
+ resetShifts,
+ calculateSnapbackTarget,
+ originalIndexes,
+ onItemDragEnd,
+ onItemDragPositionChange,
+ onItemReorder,
+ ]);
+ // Monitor drag starts to handle callbacks.
+ const onMonitorDragStart = (0, react_1.useCallback)((eventData) => {
+ const { dragged } = eventData;
+ // First, check if we need to do anything.
+ if (reorderable && dragged.parentId === id) {
+ // One of our list items is starting to be dragged.
+ const { index, originalIndex } = dragged.payload;
+ setDraggedItem(originalIndex);
+ onItemDragStart?.({
+ ...eventData,
+ index,
+ item: data?.[originalIndex],
+ });
+ }
+ }, [
+ id,
+ reorderable,
+ data,
+ setDraggedItem,
+ onItemDragStart,
+ ]);
+ // Monitor drags to react with item shifts and auto-scrolling.
+ const onMonitorDragOver = (0, react_1.useCallback)((eventData) => {
+ const { dragged, receiver, monitorOffsetRatio } = eventData;
+ // First, check if we need to shift items.
+ if (reorderable && dragged.parentId === id) {
+ // One of our list items is being dragged.
+ const fromPayload = dragged.payload;
+ // Find its current position index in the list, if any.
+ const toPayload = receiver?.parentId === id
+ ? receiver.payload
+ : undefined;
+ // Check and update currently dragged over position index.
+ const toIndex = toPayload?.index;
+ if (toIndex !== draggedToIndex.current) {
+ onItemDragPositionChange?.({
+ ...eventData,
+ toIndex,
+ index: fromPayload.index,
+ item: data?.[fromPayload.originalIndex],
+ previousIndex: draggedToIndex.current,
+ });
+ draggedToIndex.current = toIndex;
+ }
+ // Update shift transforms for items in the list.
+ updateShifts(fromPayload, toPayload ?? fromPayload);
+ }
+ // Next, see if we need to auto-scroll.
+ const ratio = horizontal ? monitorOffsetRatio.x : monitorOffsetRatio.y;
+ if (ratio > 0.1 && ratio < 0.9) {
+ scrollStateRef.current = types_1.AutoScrollDirection.None;
+ stopScroll();
+ }
+ else {
+ if (ratio >= 0.9) {
+ scrollStateRef.current = types_1.AutoScrollDirection.Forward;
+ }
+ else if (ratio <= 0.1) {
+ scrollStateRef.current = types_1.AutoScrollDirection.Back;
+ }
+ startScroll();
+ }
+ }, [
+ id,
+ reorderable,
+ data,
+ updateShifts,
+ horizontal,
+ stopScroll,
+ startScroll,
+ onItemDragPositionChange,
+ ]);
+ // Monitor drag exits to stop scrolling, update shifts, and update draggedToIndex.
+ const onMonitorDragExit = (0, react_1.useCallback)((eventData) => handleInternalDragEnd(eventData, false), [handleInternalDragEnd]);
+ /*
+ * Monitor drag ends to stop scrolling, update shifts, and possibly reorder.
+ * This addresses the Android case where if we drag a list item and auto-scroll
+ * too far, the drag gets cancelled.
+ */
+ const onMonitorDragEnd = (0, react_1.useCallback)((eventData) => handleInternalDragEnd(eventData, true), [handleInternalDragEnd]);
+ // Monitor drag drops to stop scrolling, update shifts, and possibly reorder.
+ const onMonitorDragDrop = (0, react_1.useCallback)((eventData) => handleInternalDragEnd(eventData, true), [handleInternalDragEnd]);
+ return (react_1.default.createElement(DraxView_1.DraxView, { id: id, style: style, scrollPositionRef: scrollPositionRef, onMeasure: onMeasureContainer, onMonitorDragStart: onMonitorDragStart, onMonitorDragOver: onMonitorDragOver, onMonitorDragExit: onMonitorDragExit, onMonitorDragEnd: onMonitorDragEnd, onMonitorDragDrop: onMonitorDragDrop },
+ react_1.default.createElement(DraxSubprovider_1.DraxSubprovider, { parent: { id, nodeHandleRef } },
+ react_1.default.createElement(react_native_1.FlatList, { ...flatListProps, style: flatListStyle, ref: setFlatListRefs, renderItem: renderItem, onScroll: onScroll, onContentSizeChange: onContentSizeChange, data: reorderedData }))));
+};
+exports.DraxList = (0, react_1.forwardRef)(DraxListUnforwarded);
diff --git a/build/DraxProvider.d.ts b/build/DraxProvider.d.ts
new file mode 100644
index 0000000..c03e7af
--- /dev/null
+++ b/build/DraxProvider.d.ts
@@ -0,0 +1,4 @@
+///
+///
+import { DraxProviderProps } from './types';
+export declare const DraxProvider: ({ debug, style, children, }: DraxProviderProps) => JSX.Element;
diff --git a/build/DraxProvider.js b/build/DraxProvider.js
new file mode 100644
index 0000000..7b62fc2
--- /dev/null
+++ b/build/DraxProvider.js
@@ -0,0 +1,641 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (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;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxProvider = void 0;
+const react_1 = __importStar(require("react"));
+const react_native_1 = require("react-native");
+const react_native_gesture_handler_1 = require("react-native-gesture-handler");
+const hooks_1 = require("./hooks");
+const DraxContext_1 = require("./DraxContext");
+const types_1 = require("./types");
+const math_1 = require("./math");
+const DraxProvider = ({ debug = false, style = styles.provider, children, }) => {
+ const { getViewState, getTrackingStatus, dispatch, } = (0, hooks_1.useDraxState)();
+ const { getAbsoluteViewData, getTrackingDragged, getTrackingReceiver, getTrackingMonitorIds, getTrackingMonitors, getDragPositionData, findMonitorsAndReceiver, getHoverItems, registerView, updateViewProtocol, updateViewMeasurements, resetReceiver, resetDrag, startDrag, updateDragPosition, updateReceiver, setMonitorIds, unregisterView, } = (0, hooks_1.useDraxRegistry)(dispatch);
+ const rootNodeHandleRef = (0, react_1.useRef)(null);
+ const handleGestureStateChange = (0, react_1.useCallback)((id, event) => {
+ if (debug) {
+ console.log(`handleGestureStateChange(${id}, ${JSON.stringify(event, null, 2)})`);
+ }
+ // Get info on the currently dragged view, if any.
+ const dragged = getTrackingDragged();
+ /*
+ * Case 1: We're already dragging a different view.
+ * Case 2: This view can't be found/measured.
+ * Case 3: This is the view we're already dragging.
+ * Case 3a: The drag is not ending.
+ * Case 3b: The drag is ending.
+ * Case 4: We're not already dragging a view.
+ * Case 4a: This view is not draggable.
+ * Case 4b: No drag is starting.
+ * Case 4c: A drag is starting.
+ */
+ if (dragged && dragged.id !== id) {
+ // Case 1: We're already dragging a different view.
+ if (debug) {
+ console.log(`Ignoring gesture state change because another view is being dragged: ${dragged.id}`);
+ }
+ return;
+ }
+ const draggedData = dragged?.data ?? getAbsoluteViewData(id);
+ if (!draggedData) {
+ // Case 2: This view can't be found/measured.
+ if (dragged?.id === id) {
+ if (debug) {
+ console.log(`Data for currently dragged view id ${id} could not be found`);
+ // TODO: reset drag and notify monitors
+ }
+ }
+ else if (debug) {
+ console.log(`Ignoring gesture for view id ${id} because view data was not found`);
+ }
+ return;
+ }
+ /*
+ * Documentation on gesture handler state flow used in switches below:
+ * https://github.com/kmagiera/react-native-gesture-handler/blob/master/docs/state.md
+ */
+ const { state: gestureState, // Used in switch logic below; see block comment above.
+ x: grabX, // x position of touch relative to dragged view
+ y: grabY, // y position of touch relative to dragged view
+ absoluteX: parentX, // x position of touch relative to parent of dragged view
+ absoluteY: parentY, // y position of touch relative to parent of dragged view
+ } = event;
+ /** Position of touch relative to parent of dragged view */
+ const dragParentPosition = { x: parentX, y: parentY };
+ const { x: absoluteX, // absolute x position of dragged view within DraxProvider
+ y: absoluteY, // absolute y position of dragged view within DraxProvider
+ width, // width of dragged view
+ height, // height of dragged view
+ } = draggedData.absoluteMeasurements;
+ if (dragged) {
+ // Case 3: This is the view we're already dragging.
+ let endDrag = false;
+ let cancelled = false;
+ let shouldDrop = false;
+ switch (gestureState) {
+ case react_native_gesture_handler_1.State.BEGAN:
+ // This should never happen, but we'll do nothing.
+ if (debug) {
+ console.log(`Received unexpected BEGAN event for dragged view id ${id}`);
+ }
+ break;
+ case react_native_gesture_handler_1.State.ACTIVE:
+ // This should also never happen, but we'll do nothing.
+ if (debug) {
+ console.log(`Received unexpected ACTIVE event for dragged view id ${id}`);
+ }
+ break;
+ case react_native_gesture_handler_1.State.CANCELLED:
+ // The gesture handler system has cancelled, so end the drag without dropping.
+ if (debug) {
+ console.log(`Stop dragging view id ${id} (CANCELLED)`);
+ }
+ endDrag = true;
+ cancelled = true;
+ break;
+ case react_native_gesture_handler_1.State.FAILED:
+ // This should never happen, but let's end the drag without dropping.
+ if (debug) {
+ console.log(`Received unexpected FAILED event for dragged view id ${id}`);
+ }
+ endDrag = true;
+ cancelled = true;
+ break;
+ case react_native_gesture_handler_1.State.END:
+ // User has ended the gesture, so end the drag, dropping into receiver if applicable.
+ if (debug) {
+ console.log(`Stop dragging view id ${id} (END)`);
+ }
+ endDrag = true;
+ shouldDrop = true;
+ break;
+ default:
+ if (debug) {
+ console.warn(`Unrecognized gesture state ${gestureState} for dragged view`);
+ }
+ break;
+ }
+ if (!endDrag) {
+ // Case 3a: The drag is not ending.
+ return;
+ }
+ // Case 3b: The drag is ending.
+ // Get the absolute position data for the drag touch.
+ const dragPositionData = getDragPositionData({
+ parentPosition: dragParentPosition,
+ draggedMeasurements: draggedData.absoluteMeasurements,
+ lockXPosition: draggedData.protocol.lockDragXPosition,
+ lockYPosition: draggedData.protocol.lockDragYPosition,
+ });
+ if (!dragPositionData) {
+ // Failed to get absolute position of drag. This should never happen.
+ return;
+ }
+ const { dragAbsolutePosition, dragTranslation, dragTranslationRatio, } = dragPositionData;
+ // Prepare event data for dragged view.
+ const eventDataDragged = {
+ id,
+ dragTranslationRatio,
+ parentId: draggedData.parentId,
+ payload: draggedData.protocol.dragPayload,
+ dragOffset: dragged.tracking.dragOffset,
+ grabOffset: dragged.tracking.grabOffset,
+ grabOffsetRatio: dragged.tracking.grabOffsetRatio,
+ hoverPosition: dragged.tracking.hoverPosition,
+ };
+ // Get data for receiver view (if any) before we reset.
+ const receiver = getTrackingReceiver();
+ // Get the monitors (if any) before we reset.
+ const monitors = getTrackingMonitors();
+ // Snapback target, which may be modified by responses from protocols.
+ let snapbackTarget = types_1.DraxSnapbackTargetPreset.Default;
+ if (receiver && shouldDrop) {
+ // It's a successful drop into a receiver, let them both know, and check for response.
+ let responded = false;
+ // Prepare event data for receiver view.
+ const eventDataReceiver = {
+ id: receiver.id,
+ parentId: receiver.data.parentId,
+ payload: receiver.data.protocol.receiverPayload,
+ receiveOffset: receiver.tracking.receiveOffset,
+ receiveOffsetRatio: receiver.tracking.receiveOffsetRatio,
+ };
+ const eventData = {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragged: eventDataDragged,
+ receiver: eventDataReceiver,
+ };
+ let response = draggedData.protocol.onDragDrop?.(eventData);
+ if (response !== undefined) {
+ snapbackTarget = response;
+ responded = true;
+ }
+ response = receiver.data.protocol.onReceiveDragDrop?.(eventData);
+ if (!responded && response !== undefined) {
+ snapbackTarget = response;
+ responded = true;
+ }
+ // And let any active monitors know too.
+ if (monitors.length > 0) {
+ monitors.forEach(({ data: monitorData }) => {
+ if (monitorData) {
+ const { relativePosition: monitorOffset, relativePositionRatio: monitorOffsetRatio, } = (0, math_1.getRelativePosition)(dragAbsolutePosition, monitorData.absoluteMeasurements);
+ response = monitorData.protocol.onMonitorDragDrop?.({
+ ...eventData,
+ monitorOffset,
+ monitorOffsetRatio,
+ });
+ }
+ if (!responded && response !== undefined) {
+ snapbackTarget = response;
+ responded = true;
+ }
+ });
+ }
+ }
+ else {
+ // There is no receiver, or the drag was cancelled.
+ // Prepare common event data.
+ const eventData = {
+ dragAbsolutePosition,
+ dragTranslation,
+ cancelled,
+ dragged: eventDataDragged,
+ };
+ // Let the dragged item know the drag ended, and capture any response.
+ let responded = false;
+ let response = draggedData.protocol.onDragEnd?.(eventData);
+ if (response !== undefined) {
+ snapbackTarget = response;
+ responded = true;
+ }
+ // Prepare receiver event data, or undefined if no receiver.
+ const eventReceiverData = receiver && {
+ id: receiver.id,
+ parentId: receiver.data.parentId,
+ payload: receiver.data.protocol.receiverPayload,
+ receiveOffset: receiver.tracking.receiveOffset,
+ receiveOffsetRatio: receiver.tracking.receiveOffsetRatio,
+ };
+ // If there is a receiver but drag was cancelled, let it know the drag exited it.
+ receiver?.data.protocol.onReceiveDragExit?.({
+ ...eventData,
+ receiver: eventReceiverData,
+ });
+ // And let any active monitors know too.
+ if (monitors.length > 0) {
+ const monitorEventData = {
+ ...eventData,
+ receiver: eventReceiverData,
+ };
+ monitors.forEach(({ data: monitorData }) => {
+ const { relativePosition: monitorOffset, relativePositionRatio: monitorOffsetRatio, } = (0, math_1.getRelativePosition)(dragAbsolutePosition, monitorData.absoluteMeasurements);
+ response = monitorData.protocol.onMonitorDragEnd?.({
+ ...monitorEventData,
+ monitorOffset,
+ monitorOffsetRatio,
+ cancelled,
+ });
+ if (!responded && response !== undefined) {
+ snapbackTarget = response;
+ responded = true;
+ }
+ });
+ }
+ }
+ // Reset the drag.
+ resetDrag(snapbackTarget);
+ return;
+ }
+ // Case 4: We're not already dragging a view.
+ if (!draggedData.protocol.draggable) {
+ // Case 4a: This view is not draggable.
+ return;
+ }
+ let shouldStartDrag = false;
+ switch (gestureState) {
+ case react_native_gesture_handler_1.State.ACTIVE:
+ shouldStartDrag = true;
+ break;
+ case react_native_gesture_handler_1.State.BEGAN:
+ // Do nothing until the gesture becomes active.
+ break;
+ case react_native_gesture_handler_1.State.CANCELLED:
+ case react_native_gesture_handler_1.State.FAILED:
+ case react_native_gesture_handler_1.State.END:
+ // Do nothing because we weren't tracking this gesture.
+ break;
+ default:
+ if (debug) {
+ console.warn(`Unrecognized gesture state ${gestureState} for non-dragged view id ${id}`);
+ }
+ break;
+ }
+ if (!shouldStartDrag) {
+ // Case 4b: No drag is starting.
+ return;
+ }
+ // Case 4c: A drag is starting.
+ /*
+ * First, verify that the touch is still within the dragged view.
+ * Because we are using a LongPressGestureHandler with unlimited
+ * distance to handle the drag, it could be out of bounds before
+ * it even starts. (For some reason, LongPressGestureHandler does
+ * not provide us with a BEGAN state change event in iOS.)
+ */
+ if (grabX >= 0 && grabY >= 0 && grabX < width && grabY < height) {
+ /*
+ * To determine drag start position in absolute coordinates, we add:
+ * absolute coordinates of dragged view
+ * + relative coordinates of touch within view
+ *
+ * NOTE: if view is transformed, these will be wrong.
+ */
+ const dragAbsolutePosition = {
+ x: absoluteX + grabX,
+ y: absoluteY + grabY,
+ };
+ const grabOffset = { x: grabX, y: grabY };
+ const grabOffsetRatio = {
+ x: grabX / width,
+ y: grabY / height,
+ };
+ const { dragOffset, dragTranslation, dragTranslationRatio, hoverPosition, } = startDrag({
+ grabOffset,
+ grabOffsetRatio,
+ dragAbsolutePosition,
+ dragParentPosition,
+ draggedId: id,
+ });
+ if (debug) {
+ console.log(`Start dragging view id ${id} at absolute position (${dragAbsolutePosition.x}, ${dragAbsolutePosition.y})`);
+ }
+ const eventData = {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragged: {
+ id,
+ dragOffset,
+ grabOffset,
+ grabOffsetRatio,
+ hoverPosition,
+ dragTranslationRatio,
+ parentId: draggedData.parentId,
+ payload: draggedData.protocol.dragPayload,
+ },
+ };
+ draggedData.protocol.onDragStart?.(eventData);
+ // Find which monitors and receiver this drag is over.
+ const { monitors } = findMonitorsAndReceiver(dragAbsolutePosition, id);
+ // Notify monitors and update monitor tracking.
+ if (monitors.length > 0) {
+ const newMonitorIds = monitors.map(({ id: monitorId, data: monitorData, relativePosition: monitorOffset, relativePositionRatio: monitorOffsetRatio, }) => {
+ const monitorEventData = {
+ ...eventData,
+ monitorOffset,
+ monitorOffsetRatio,
+ };
+ monitorData.protocol.onMonitorDragStart?.(monitorEventData);
+ return monitorId;
+ });
+ setMonitorIds(newMonitorIds);
+ }
+ }
+ }, [
+ getAbsoluteViewData,
+ getDragPositionData,
+ getTrackingDragged,
+ getTrackingReceiver,
+ getTrackingMonitors,
+ resetDrag,
+ startDrag,
+ findMonitorsAndReceiver,
+ setMonitorIds,
+ debug,
+ ]);
+ const handleGestureEvent = (0, react_1.useCallback)((id, event) => {
+ if (debug) {
+ console.log(`handleGestureEvent(${id}, ${JSON.stringify(event, null, 2)})`);
+ }
+ const dragged = getTrackingDragged();
+ if (dragged === undefined) {
+ // We're not tracking any gesture yet.
+ if (debug) {
+ console.log('Ignoring gesture event because we have not initialized a drag');
+ }
+ return;
+ }
+ if (dragged.id !== id) {
+ // This is not a gesture we're tracking. We don't support multiple simultaneous drags.
+ if (debug) {
+ console.log('Ignoring gesture event because this is not the view being dragged');
+ }
+ return;
+ }
+ const { absoluteX: parentX, // x position of touch relative to parent of dragged view
+ absoluteY: parentY, // y position of touch relative to parent of dragged view
+ } = event;
+ if (debug) {
+ console.log(`Dragged item absolute coordinates (${dragged.data.absoluteMeasurements.x}, ${dragged.data.absoluteMeasurements.y})`);
+ console.log(`Native event in-view touch coordinates: (${event.x}, ${event.y})`);
+ }
+ /** Position of touch relative to parent of dragged view */
+ const parentPosition = { x: parentX, y: parentY };
+ // Get the absolute position data for the drag touch.
+ const dragPositionData = getDragPositionData({
+ parentPosition,
+ draggedMeasurements: dragged.data.absoluteMeasurements,
+ lockXPosition: dragged.data.protocol.lockDragXPosition,
+ lockYPosition: dragged.data.protocol.lockDragYPosition,
+ });
+ if (!dragPositionData) {
+ // Failed to get drag position data. This should never happen.
+ return;
+ }
+ const { dragAbsolutePosition, dragTranslation, dragTranslationRatio, } = dragPositionData;
+ if (debug) {
+ console.log(`Drag at absolute coordinates (${dragAbsolutePosition.x}, ${dragAbsolutePosition.y})\n`);
+ console.log(`Drag translation (${dragTranslation.x}, ${dragTranslation.y})`);
+ console.log(`Drag translation ratio (${dragTranslationRatio.x}, ${dragTranslationRatio.y})`);
+ }
+ // Find which monitors and receiver this drag is over.
+ const { monitors, receiver } = findMonitorsAndReceiver(dragAbsolutePosition, dragged.id);
+ // Get the previous receiver, if any.
+ const oldReceiver = getTrackingReceiver();
+ // Always update the drag position.
+ updateDragPosition(dragAbsolutePosition);
+ const draggedProtocol = dragged.data.protocol;
+ // Prepare event data for dragged view.
+ const eventDataDragged = {
+ dragTranslationRatio,
+ id: dragged.id,
+ parentId: dragged.data.parentId,
+ payload: dragged.data.protocol.dragPayload,
+ dragOffset: dragged.tracking.dragOffset,
+ grabOffset: dragged.tracking.grabOffset,
+ grabOffsetRatio: dragged.tracking.grabOffsetRatio,
+ hoverPosition: dragged.tracking.hoverPosition,
+ };
+ // Prepare base drag event data.
+ const dragEventData = {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragged: eventDataDragged,
+ };
+ // Prepare event data stub for monitor updates later so we can optionally add receiver.
+ const monitorEventDataStub = {
+ ...dragEventData,
+ };
+ /*
+ * Consider the following cases for new and old receiver ids:
+ * Case 1: new exists, old exists, new is the same as old
+ * Case 2: new exists, old exists, new is different from old
+ * Case 3: new exists, old does not exist
+ * Case 4: new does not exist, old exists
+ * Case 5: new does not exist, old does not exist
+ */
+ if (receiver) {
+ // New receiver exists.
+ const receiverProtocol = receiver.data.protocol;
+ // Update the receiver.
+ const trackingReceiver = updateReceiver(receiver, dragged);
+ if (trackingReceiver === undefined) {
+ // This should never happen, but just in case.
+ if (debug) {
+ console.log('Failed to update tracking receiver');
+ }
+ return;
+ }
+ // Prepare event data for receiver view.
+ const eventDataReceiver = {
+ id: receiver.id,
+ parentId: receiver.data.parentId,
+ payload: receiver.data.protocol.receiverPayload,
+ receiveOffset: trackingReceiver.receiveOffset,
+ receiveOffsetRatio: trackingReceiver.receiveOffsetRatio,
+ };
+ // Add receiver data to monitor event stub.
+ monitorEventDataStub.receiver = eventDataReceiver;
+ // Prepare event data for callbacks.
+ const eventData = {
+ ...dragEventData,
+ receiver: eventDataReceiver,
+ };
+ if (oldReceiver) {
+ if (receiver.id === oldReceiver.id) {
+ // Case 1: new exists, old exists, new is the same as old
+ // Call the protocol event callbacks for dragging over the receiver.
+ draggedProtocol.onDragOver?.(eventData);
+ receiverProtocol.onReceiveDragOver?.(eventData);
+ }
+ else {
+ // Case 2: new exists, old exists, new is different from old
+ // Prepare event data with old receiver.
+ const eventDataOldReceiver = {
+ ...dragEventData,
+ receiver: {
+ id: oldReceiver.id,
+ parentId: oldReceiver.data.parentId,
+ payload: oldReceiver.data.protocol.receiverPayload,
+ receiveOffset: oldReceiver.tracking.receiveOffset,
+ receiveOffsetRatio: oldReceiver.tracking.receiveOffsetRatio,
+ },
+ };
+ // Call the protocol event callbacks for exiting the old receiver...
+ draggedProtocol.onDragExit?.(eventDataOldReceiver);
+ oldReceiver.data.protocol.onReceiveDragExit?.({
+ ...eventDataOldReceiver,
+ cancelled: false,
+ });
+ // ...and entering the new receiver.
+ draggedProtocol.onDragEnter?.(eventData);
+ receiverProtocol.onReceiveDragEnter?.(eventData);
+ }
+ }
+ else {
+ // Case 3: new exists, old does not exist
+ // Call the protocol event callbacks for entering the new receiver.
+ draggedProtocol.onDragEnter?.(eventData);
+ receiverProtocol.onReceiveDragEnter?.(eventData);
+ }
+ }
+ else if (oldReceiver) {
+ // Case 4: new does not exist, old exists
+ // Reset the old receiver.
+ resetReceiver();
+ // Prepare event data with old receiver.
+ const eventData = {
+ ...dragEventData,
+ receiver: {
+ id: oldReceiver.id,
+ parentId: oldReceiver.data.parentId,
+ payload: oldReceiver.data.protocol.receiverPayload,
+ receiveOffset: oldReceiver.tracking.receiveOffset,
+ receiveOffsetRatio: oldReceiver.tracking.receiveOffsetRatio,
+ },
+ };
+ // Call the protocol event callbacks for exiting the old receiver.
+ draggedProtocol.onDragExit?.(eventData);
+ oldReceiver.data.protocol.onReceiveDragExit?.({
+ ...eventData,
+ cancelled: false,
+ });
+ }
+ else {
+ // Case 5: new does not exist, old does not exist
+ // Call the protocol event callback for dragging.
+ draggedProtocol.onDrag?.(dragEventData);
+ }
+ // Notify monitors and update monitor tracking, if necessary.
+ const prevMonitorIds = getTrackingMonitorIds();
+ if (monitors.length > 0 || prevMonitorIds.length > 0) {
+ const newMonitorIds = monitors.map(({ id: monitorId, data: monitorData, relativePosition: monitorOffset, relativePositionRatio: monitorOffsetRatio, }) => {
+ const monitorEventData = {
+ ...monitorEventDataStub,
+ monitorOffset,
+ monitorOffsetRatio,
+ };
+ if (prevMonitorIds.includes(monitorId)) {
+ // Drag was already over this monitor.
+ monitorData.protocol.onMonitorDragOver?.(monitorEventData);
+ }
+ else {
+ // Drag is entering monitor.
+ monitorData.protocol.onMonitorDragEnter?.(monitorEventData);
+ }
+ return monitorId;
+ });
+ prevMonitorIds.filter((monitorId) => !newMonitorIds.includes(monitorId))
+ .forEach((monitorId) => {
+ // Drag has exited monitor.
+ const monitorData = getAbsoluteViewData(monitorId);
+ if (monitorData) {
+ const { relativePosition: monitorOffset, relativePositionRatio: monitorOffsetRatio, } = (0, math_1.getRelativePosition)(dragAbsolutePosition, monitorData.absoluteMeasurements);
+ monitorData.protocol.onMonitorDragExit?.({
+ ...monitorEventDataStub,
+ monitorOffset,
+ monitorOffsetRatio,
+ });
+ }
+ });
+ setMonitorIds(newMonitorIds);
+ }
+ }, [
+ getAbsoluteViewData,
+ getDragPositionData,
+ getTrackingDragged,
+ getTrackingReceiver,
+ getTrackingMonitorIds,
+ findMonitorsAndReceiver,
+ resetReceiver,
+ updateDragPosition,
+ updateReceiver,
+ setMonitorIds,
+ debug,
+ ]);
+ const contextValue = {
+ getViewState,
+ getTrackingStatus,
+ registerView,
+ unregisterView,
+ updateViewProtocol,
+ updateViewMeasurements,
+ handleGestureStateChange,
+ handleGestureEvent,
+ rootNodeHandleRef,
+ };
+ const hoverViews = [];
+ const trackingStatus = getTrackingStatus();
+ getHoverItems().forEach(({ id, key, internalRenderHoverView, hoverPosition, dimensions, }) => {
+ const viewState = getViewState(id);
+ if (viewState) {
+ const hoverView = internalRenderHoverView({
+ key,
+ hoverPosition,
+ viewState,
+ trackingStatus,
+ dimensions,
+ });
+ if (hoverView) {
+ hoverViews.push(hoverView);
+ }
+ }
+ });
+ const setRootNodeHandleRef = (0, react_1.useCallback)((ref) => {
+ rootNodeHandleRef.current = ref && (0, react_native_1.findNodeHandle)(ref);
+ }, []);
+ return (react_1.default.createElement(DraxContext_1.DraxContext.Provider, { value: contextValue },
+ react_1.default.createElement(react_native_1.View, { style: style, ref: setRootNodeHandleRef },
+ children,
+ react_1.default.createElement(react_native_1.View, { style: react_native_1.StyleSheet.absoluteFill, pointerEvents: "none" }, hoverViews))));
+};
+exports.DraxProvider = DraxProvider;
+const styles = react_native_1.StyleSheet.create({
+ provider: {
+ flex: 1,
+ },
+});
diff --git a/build/DraxScrollView.d.ts b/build/DraxScrollView.d.ts
new file mode 100644
index 0000000..bf6f087
--- /dev/null
+++ b/build/DraxScrollView.d.ts
@@ -0,0 +1,6 @@
+import React from 'react';
+import { ScrollView } from 'react-native';
+import { DraxScrollViewProps } from './types';
+export declare const DraxScrollView: React.ForwardRefExoticComponent>;
diff --git a/build/DraxScrollView.js b/build/DraxScrollView.js
new file mode 100644
index 0000000..c7847ab
--- /dev/null
+++ b/build/DraxScrollView.js
@@ -0,0 +1,197 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (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;
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxScrollView = void 0;
+const react_1 = __importStar(require("react"));
+const react_native_1 = require("react-native");
+const DraxView_1 = require("./DraxView");
+const DraxSubprovider_1 = require("./DraxSubprovider");
+const hooks_1 = require("./hooks");
+const types_1 = require("./types");
+const params_1 = require("./params");
+const DraxScrollViewUnforwarded = (props, forwardedRef) => {
+ const { children, style, onScroll: onScrollProp, onContentSizeChange: onContentSizeChangeProp, scrollEventThrottle = params_1.defaultScrollEventThrottle, autoScrollIntervalLength = params_1.defaultAutoScrollIntervalLength, autoScrollJumpRatio = params_1.defaultAutoScrollJumpRatio, autoScrollBackThreshold = params_1.defaultAutoScrollBackThreshold, autoScrollForwardThreshold = params_1.defaultAutoScrollForwardThreshold, id: idProp, ...scrollViewProps } = props;
+ // The unique identifer for this view.
+ const id = (0, hooks_1.useDraxId)(idProp);
+ // Scrollable view, used for scrolling.
+ const scrollRef = (0, react_1.useRef)(null);
+ // ScrollView node handle, used for measuring children.
+ const nodeHandleRef = (0, react_1.useRef)(null);
+ // Container view measurements, for scrolling by percentage.
+ const containerMeasurementsRef = (0, react_1.useRef)(undefined);
+ // Content size, for scrolling by percentage.
+ const contentSizeRef = (0, react_1.useRef)(undefined);
+ // Scroll position, for Drax bounds checking and auto-scrolling.
+ const scrollPositionRef = (0, react_1.useRef)({ x: 0, y: 0 });
+ // Auto-scroll state.
+ const autoScrollStateRef = (0, react_1.useRef)({
+ x: types_1.AutoScrollDirection.None,
+ y: types_1.AutoScrollDirection.None,
+ });
+ // Auto-scroll interval.
+ const autoScrollIntervalRef = (0, react_1.useRef)(undefined);
+ // Handle auto-scrolling on interval.
+ const doScroll = (0, react_1.useCallback)(() => {
+ const scroll = scrollRef.current;
+ const containerMeasurements = containerMeasurementsRef.current;
+ const contentSize = contentSizeRef.current;
+ if (!scroll || !containerMeasurements || !contentSize) {
+ return;
+ }
+ const scrollPosition = scrollPositionRef.current;
+ const autoScrollState = autoScrollStateRef.current;
+ const jump = {
+ x: containerMeasurements.width * autoScrollJumpRatio,
+ y: containerMeasurements.height * autoScrollJumpRatio,
+ };
+ let xNew;
+ let yNew;
+ if (autoScrollState.x === types_1.AutoScrollDirection.Forward) {
+ const xMax = contentSize.x - containerMeasurements.width;
+ if (scrollPosition.x < xMax) {
+ xNew = Math.min(scrollPosition.x + jump.x, xMax);
+ }
+ }
+ else if (autoScrollState.x === types_1.AutoScrollDirection.Back) {
+ if (scrollPosition.x > 0) {
+ xNew = Math.max(scrollPosition.x - jump.x, 0);
+ }
+ }
+ if (autoScrollState.y === types_1.AutoScrollDirection.Forward) {
+ const yMax = contentSize.y - containerMeasurements.height;
+ if (scrollPosition.y < yMax) {
+ yNew = Math.min(scrollPosition.y + jump.y, yMax);
+ }
+ }
+ else if (autoScrollState.y === types_1.AutoScrollDirection.Back) {
+ if (scrollPosition.y > 0) {
+ yNew = Math.max(scrollPosition.y - jump.y, 0);
+ }
+ }
+ if (xNew !== undefined || yNew !== undefined) {
+ scroll.scrollTo({
+ x: xNew ?? scrollPosition.x,
+ y: yNew ?? scrollPosition.y,
+ });
+ scroll.flashScrollIndicators(); // ScrollView typing is missing this method
+ }
+ }, [autoScrollJumpRatio]);
+ // Start the auto-scrolling interval.
+ const startScroll = (0, react_1.useCallback)(() => {
+ if (autoScrollIntervalRef.current) {
+ return;
+ }
+ doScroll();
+ autoScrollIntervalRef.current = setInterval(doScroll, autoScrollIntervalLength);
+ }, [doScroll, autoScrollIntervalLength]);
+ // Stop the auto-scrolling interval.
+ const stopScroll = (0, react_1.useCallback)(() => {
+ if (autoScrollIntervalRef.current) {
+ clearInterval(autoScrollIntervalRef.current);
+ autoScrollIntervalRef.current = undefined;
+ }
+ }, []);
+ // If startScroll changes, refresh our interval.
+ (0, react_1.useEffect)(() => {
+ if (autoScrollIntervalRef.current) {
+ stopScroll();
+ startScroll();
+ }
+ }, [stopScroll, startScroll]);
+ // Clear auto-scroll direction and stop the auto-scrolling interval.
+ const resetScroll = (0, react_1.useCallback)(() => {
+ const autoScrollState = autoScrollStateRef.current;
+ autoScrollState.x = types_1.AutoScrollDirection.None;
+ autoScrollState.y = types_1.AutoScrollDirection.None;
+ stopScroll();
+ }, [stopScroll]);
+ // Track the size of the container view.
+ const onMeasureContainer = (0, react_1.useCallback)((measurements) => {
+ containerMeasurementsRef.current = measurements;
+ }, []);
+ // Monitor drag-over events to react with auto-scrolling.
+ const onMonitorDragOver = (0, react_1.useCallback)((event) => {
+ const { monitorOffsetRatio } = event;
+ const autoScrollState = autoScrollStateRef.current;
+ if (monitorOffsetRatio.x >= autoScrollForwardThreshold) {
+ autoScrollState.x = types_1.AutoScrollDirection.Forward;
+ }
+ else if (monitorOffsetRatio.x <= autoScrollBackThreshold) {
+ autoScrollState.x = types_1.AutoScrollDirection.Back;
+ }
+ else {
+ autoScrollState.x = types_1.AutoScrollDirection.None;
+ }
+ if (monitorOffsetRatio.y >= autoScrollForwardThreshold) {
+ autoScrollState.y = types_1.AutoScrollDirection.Forward;
+ }
+ else if (monitorOffsetRatio.y <= autoScrollBackThreshold) {
+ autoScrollState.y = types_1.AutoScrollDirection.Back;
+ }
+ else {
+ autoScrollState.y = types_1.AutoScrollDirection.None;
+ }
+ if (autoScrollState.x === types_1.AutoScrollDirection.None && autoScrollState.y === types_1.AutoScrollDirection.None) {
+ stopScroll();
+ }
+ else {
+ startScroll();
+ }
+ }, [
+ stopScroll,
+ startScroll,
+ autoScrollBackThreshold,
+ autoScrollForwardThreshold,
+ ]);
+ // Set the ScrollView and node handle refs.
+ const setScrollViewRefs = (0, react_1.useCallback)((ref) => {
+ scrollRef.current = ref;
+ nodeHandleRef.current = ref && (0, react_native_1.findNodeHandle)(ref);
+ if (forwardedRef) {
+ if (typeof forwardedRef === 'function') {
+ forwardedRef(ref);
+ }
+ else {
+ // eslint-disable-next-line no-param-reassign
+ forwardedRef.current = ref;
+ }
+ }
+ }, [forwardedRef]);
+ // Track content size.
+ const onContentSizeChange = (0, react_1.useCallback)((width, height) => {
+ contentSizeRef.current = { x: width, y: height };
+ return onContentSizeChangeProp?.(width, height);
+ }, [onContentSizeChangeProp]);
+ // Update tracked scroll position when list is scrolled.
+ const onScroll = (0, react_1.useCallback)((event) => {
+ const { nativeEvent: { contentOffset } } = event;
+ scrollPositionRef.current = { ...contentOffset };
+ return onScrollProp?.(event);
+ }, [onScrollProp]);
+ return id ? (react_1.default.createElement(DraxView_1.DraxView, { id: id, style: style, scrollPositionRef: scrollPositionRef, onMeasure: onMeasureContainer, onMonitorDragOver: onMonitorDragOver, onMonitorDragExit: resetScroll, onMonitorDragEnd: resetScroll, onMonitorDragDrop: resetScroll },
+ react_1.default.createElement(DraxSubprovider_1.DraxSubprovider, { parent: { id, nodeHandleRef } },
+ react_1.default.createElement(react_native_1.ScrollView, { ...scrollViewProps, ref: setScrollViewRefs, onContentSizeChange: onContentSizeChange, onScroll: onScroll, scrollEventThrottle: scrollEventThrottle }, children)))) : null;
+};
+exports.DraxScrollView = (0, react_1.forwardRef)(DraxScrollViewUnforwarded);
diff --git a/build/DraxSubprovider.d.ts b/build/DraxSubprovider.d.ts
new file mode 100644
index 0000000..93ca221
--- /dev/null
+++ b/build/DraxSubprovider.d.ts
@@ -0,0 +1,3 @@
+import { FunctionComponent } from 'react';
+import { DraxSubproviderProps } from './types';
+export declare const DraxSubprovider: FunctionComponent;
diff --git a/build/DraxSubprovider.js b/build/DraxSubprovider.js
new file mode 100644
index 0000000..b9fa06d
--- /dev/null
+++ b/build/DraxSubprovider.js
@@ -0,0 +1,18 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxSubprovider = void 0;
+const react_1 = __importDefault(require("react"));
+const DraxContext_1 = require("./DraxContext");
+const hooks_1 = require("./hooks");
+const DraxSubprovider = ({ parent, children }) => {
+ const contextValue = (0, hooks_1.useDraxContext)();
+ const subContextValue = {
+ ...contextValue,
+ parent,
+ };
+ return (react_1.default.createElement(DraxContext_1.DraxContext.Provider, { value: subContextValue }, children));
+};
+exports.DraxSubprovider = DraxSubprovider;
diff --git a/build/DraxView.d.ts b/build/DraxView.d.ts
new file mode 100644
index 0000000..d89a58a
--- /dev/null
+++ b/build/DraxView.d.ts
@@ -0,0 +1,3 @@
+import { PropsWithChildren } from 'react';
+import { DraxViewProps } from './types';
+export declare const DraxView: ({ onDragStart, onDrag, onDragEnter, onDragOver, onDragExit, onDragEnd, onDragDrop, onSnapbackEnd, onReceiveDragEnter, onReceiveDragOver, onReceiveDragExit, onReceiveDragDrop, onMonitorDragStart, onMonitorDragEnter, onMonitorDragOver, onMonitorDragExit, onMonitorDragEnd, onMonitorDragDrop, animateSnapback, snapbackDelay, snapbackDuration, snapbackAnimator, payload, dragPayload, receiverPayload, style, dragInactiveStyle, draggingStyle, draggingWithReceiverStyle, draggingWithoutReceiverStyle, dragReleasedStyle, hoverStyle, hoverDraggingStyle, hoverDraggingWithReceiverStyle, hoverDraggingWithoutReceiverStyle, hoverDragReleasedStyle, receiverInactiveStyle, receivingStyle, otherDraggingStyle, otherDraggingWithReceiverStyle, otherDraggingWithoutReceiverStyle, renderContent, renderHoverContent, registration, onMeasure, scrollPositionRef, lockDragXPosition, lockDragYPosition, children, viewRef: inputViewRef, noHover, isParent, longPressDelay, id: idProp, parent: parentProp, draggable: draggableProp, receptive: receptiveProp, monitoring: monitoringProp, ...props }: PropsWithChildren) => JSX.Element;
diff --git a/build/DraxView.js b/build/DraxView.js
new file mode 100644
index 0000000..abd893c
--- /dev/null
+++ b/build/DraxView.js
@@ -0,0 +1,424 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (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 __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxView = void 0;
+const react_1 = __importStar(require("react"));
+const react_native_1 = require("react-native");
+const react_native_gesture_handler_1 = require("react-native-gesture-handler");
+const lodash_throttle_1 = __importDefault(require("lodash.throttle"));
+const hooks_1 = require("./hooks");
+const types_1 = require("./types");
+const params_1 = require("./params");
+const math_1 = require("./math");
+const DraxSubprovider_1 = require("./DraxSubprovider");
+const transform_1 = require("./transform");
+const DraxView = ({ onDragStart, onDrag, onDragEnter, onDragOver, onDragExit, onDragEnd, onDragDrop, onSnapbackEnd, onReceiveDragEnter, onReceiveDragOver, onReceiveDragExit, onReceiveDragDrop, onMonitorDragStart, onMonitorDragEnter, onMonitorDragOver, onMonitorDragExit, onMonitorDragEnd, onMonitorDragDrop, animateSnapback, snapbackDelay, snapbackDuration, snapbackAnimator, payload, dragPayload, receiverPayload, style, dragInactiveStyle, draggingStyle, draggingWithReceiverStyle, draggingWithoutReceiverStyle, dragReleasedStyle, hoverStyle, hoverDraggingStyle, hoverDraggingWithReceiverStyle, hoverDraggingWithoutReceiverStyle, hoverDragReleasedStyle, receiverInactiveStyle, receivingStyle, otherDraggingStyle, otherDraggingWithReceiverStyle, otherDraggingWithoutReceiverStyle, renderContent, renderHoverContent, registration, onMeasure, scrollPositionRef, lockDragXPosition, lockDragYPosition, children, viewRef: inputViewRef, noHover = false, isParent = false, longPressDelay = params_1.defaultLongPressDelay, id: idProp, parent: parentProp, draggable: draggableProp, receptive: receptiveProp, monitoring: monitoringProp, ...props }) => {
+ // Coalesce protocol props into capabilities.
+ const draggable = draggableProp ?? (dragPayload !== undefined
+ || payload !== undefined
+ || !!onDrag
+ || !!onDragEnd
+ || !!onDragEnter
+ || !!onDragExit
+ || !!onDragOver
+ || !!onDragStart
+ || !!onDragDrop);
+ const receptive = receptiveProp ?? (receiverPayload !== undefined
+ || payload !== undefined
+ || !!onReceiveDragEnter
+ || !!onReceiveDragExit
+ || !!onReceiveDragOver
+ || !!onReceiveDragDrop);
+ const monitoring = monitoringProp ?? (!!onMonitorDragStart
+ || !!onMonitorDragEnter
+ || !!onMonitorDragOver
+ || !!onMonitorDragExit
+ || !!onMonitorDragEnd
+ || !!onMonitorDragDrop);
+ // The unique identifier for this view.
+ const id = (0, hooks_1.useDraxId)(idProp);
+ // The underlying View, for measuring.
+ const viewRef = (0, react_1.useRef)(null);
+ // The underlying View node handle, used for subprovider nesting if this is a Drax parent view.
+ const nodeHandleRef = (0, react_1.useRef)(null);
+ // This view's measurements, for reference.
+ const measurementsRef = (0, react_1.useRef)(undefined);
+ // Connect with Drax.
+ const { getViewState, getTrackingStatus, registerView, unregisterView, updateViewProtocol, updateViewMeasurements, handleGestureEvent, handleGestureStateChange, rootNodeHandleRef, parent: contextParent, } = (0, hooks_1.useDraxContext)();
+ // Identify Drax parent view (if any) from context or prop override.
+ const parent = parentProp ?? contextParent;
+ const parentId = parent?.id;
+ // Identify parent node handle ref.
+ const parentNodeHandleRef = parent ? parent.nodeHandleRef : rootNodeHandleRef;
+ // Register and unregister with Drax context when necessary.
+ (0, react_1.useEffect)(() => {
+ // Register with Drax context after we have an id.
+ registerView({ id, parentId, scrollPositionRef });
+ // Unregister when we unmount or id changes.
+ return () => unregisterView({ id });
+ }, [
+ id,
+ parentId,
+ scrollPositionRef,
+ registerView,
+ unregisterView,
+ ]);
+ // Combine hover styles for given internal render props.
+ const getCombinedHoverStyle = (0, react_1.useCallback)(({ viewState: { dragStatus }, trackingStatus: { receiving: anyReceiving }, hoverPosition, dimensions, }) => {
+ // Start with base style, calculated dimensions, and hover base style.
+ const hoverStyles = [
+ style,
+ dimensions,
+ hoverStyle,
+ ];
+ // Apply style style overrides based on state.
+ if (dragStatus === types_1.DraxViewDragStatus.Dragging) {
+ hoverStyles.push(hoverDraggingStyle);
+ if (anyReceiving) {
+ hoverStyles.push(hoverDraggingWithReceiverStyle);
+ }
+ else {
+ hoverStyles.push(hoverDraggingWithoutReceiverStyle);
+ }
+ }
+ else if (dragStatus === types_1.DraxViewDragStatus.Released) {
+ hoverStyles.push(hoverDragReleasedStyle);
+ }
+ // Remove any layout styles.
+ const flattenedHoverStyle = (0, transform_1.flattenStylesWithoutLayout)(hoverStyles);
+ // Apply hover transform.
+ const transform = hoverPosition.getTranslateTransform();
+ return (0, transform_1.mergeStyleTransform)(flattenedHoverStyle, transform);
+ }, [
+ style,
+ hoverStyle,
+ hoverDraggingStyle,
+ hoverDraggingWithReceiverStyle,
+ hoverDraggingWithoutReceiverStyle,
+ hoverDragReleasedStyle,
+ ]);
+ // Internal render function for hover views, used in protocol by provider.
+ const internalRenderHoverView = (0, react_1.useMemo)(() => ((draggable && !noHover)
+ ? (internalProps) => {
+ let content;
+ const render = renderHoverContent ?? renderContent;
+ if (render) {
+ const renderProps = {
+ children,
+ hover: true,
+ viewState: internalProps.viewState,
+ trackingStatus: internalProps.trackingStatus,
+ dimensions: internalProps.dimensions,
+ };
+ content = render(renderProps);
+ }
+ else {
+ content = children;
+ }
+ return (react_1.default.createElement(react_native_1.Animated.View, { ...props, key: internalProps.key, style: getCombinedHoverStyle(internalProps) }, content));
+ }
+ : undefined), [
+ draggable,
+ noHover,
+ renderHoverContent,
+ renderContent,
+ getCombinedHoverStyle,
+ props,
+ children,
+ ]);
+ // Report updates to our protocol callbacks when we have an id and whenever the props change.
+ (0, react_1.useEffect)(() => {
+ updateViewProtocol({
+ id,
+ protocol: {
+ onDragStart,
+ onDrag,
+ onDragEnter,
+ onDragOver,
+ onDragExit,
+ onDragEnd,
+ onDragDrop,
+ onSnapbackEnd,
+ onReceiveDragEnter,
+ onReceiveDragOver,
+ onReceiveDragExit,
+ onReceiveDragDrop,
+ onMonitorDragStart,
+ onMonitorDragEnter,
+ onMonitorDragOver,
+ onMonitorDragExit,
+ onMonitorDragEnd,
+ onMonitorDragDrop,
+ animateSnapback,
+ snapbackDelay,
+ snapbackDuration,
+ snapbackAnimator,
+ internalRenderHoverView,
+ draggable,
+ receptive,
+ monitoring,
+ lockDragXPosition,
+ lockDragYPosition,
+ dragPayload: dragPayload ?? payload,
+ receiverPayload: receiverPayload ?? payload,
+ },
+ });
+ }, [
+ id,
+ updateViewProtocol,
+ children,
+ onDragStart,
+ onDrag,
+ onDragEnter,
+ onDragOver,
+ onDragExit,
+ onDragEnd,
+ onDragDrop,
+ onSnapbackEnd,
+ onReceiveDragEnter,
+ onReceiveDragOver,
+ onReceiveDragExit,
+ onReceiveDragDrop,
+ onMonitorDragStart,
+ onMonitorDragEnter,
+ onMonitorDragOver,
+ onMonitorDragExit,
+ onMonitorDragEnd,
+ onMonitorDragDrop,
+ animateSnapback,
+ snapbackDelay,
+ snapbackDuration,
+ snapbackAnimator,
+ payload,
+ dragPayload,
+ receiverPayload,
+ draggable,
+ receptive,
+ monitoring,
+ lockDragXPosition,
+ lockDragYPosition,
+ internalRenderHoverView,
+ ]);
+ // Connect gesture state change handling into Drax context, tied to this id.
+ const onHandlerStateChange = (0, react_1.useCallback)(({ nativeEvent }) => handleGestureStateChange(id, nativeEvent), [id, handleGestureStateChange]);
+ // Create throttled gesture event handler, tied to this id.
+ const throttledHandleGestureEvent = (0, react_1.useMemo)(() => (0, lodash_throttle_1.default)((event) => {
+ // Pass the event up to the Drax context.
+ handleGestureEvent(id, event);
+ }, 10), [id, handleGestureEvent]);
+ // Connect gesture event handling into Drax context, extracting nativeEvent.
+ const onGestureEvent = (0, react_1.useCallback)(({ nativeEvent }) => throttledHandleGestureEvent(nativeEvent), [throttledHandleGestureEvent]);
+ // Build a callback which will report our measurements to Drax context,
+ // onMeasure, and an optional measurement handler.
+ const buildMeasureCallback = (0, react_1.useCallback)((measurementHandler) => ((x, y, width, height) => {
+ /*
+ * In certain cases (on Android), all of these values can be
+ * undefined when the view is not on screen; This should not
+ * happen with the measurement functions we're using, but just
+ * for the sake of paranoia, we'll check and use undefined
+ * for the entire measurements object.
+ */
+ const measurements = (height === undefined
+ ? undefined
+ : {
+ height,
+ x: x,
+ y: y,
+ width: width,
+ });
+ measurementsRef.current = measurements;
+ updateViewMeasurements({ id, measurements });
+ onMeasure?.(measurements);
+ measurementHandler?.(measurements);
+ }), [id, updateViewMeasurements, onMeasure]);
+ // Callback which will report our measurements to Drax context and onMeasure.
+ const updateMeasurements = (0, react_1.useMemo)(() => buildMeasureCallback(), [buildMeasureCallback]);
+ // Measure and report our measurements to Drax context, onMeasure, and an
+ // optional measurement handler on demand.
+ const measureWithHandler = (0, react_1.useCallback)((measurementHandler) => {
+ const view = viewRef.current;
+ if (view) {
+ const nodeHandle = parentNodeHandleRef.current;
+ if (nodeHandle) {
+ const measureCallback = measurementHandler
+ ? buildMeasureCallback(measurementHandler)
+ : updateMeasurements;
+ // console.log('definitely measuring in reference to something');
+ view.measureLayout(nodeHandle, measureCallback, () => {
+ // console.log('Failed to measure Drax view in relation to parent nodeHandle');
+ });
+ }
+ else {
+ // console.log('No parent nodeHandle to measure Drax view in relation to');
+ }
+ }
+ else {
+ // console.log('No view to measure');
+ }
+ }, [
+ parentNodeHandleRef,
+ buildMeasureCallback,
+ updateMeasurements,
+ ]);
+ // Measure and send our measurements to Drax context and onMeasure, used when this view finishes layout.
+ const onLayout = (0, react_1.useCallback)(() => {
+ // console.log(`onLayout ${id}`);
+ measureWithHandler();
+ }, [measureWithHandler]);
+ // Establish dimensions/orientation change handler when necessary.
+ (0, react_1.useEffect)(() => {
+ const handler = ( /* { screen: { width, height } }: { screen: ScaledSize } */) => {
+ // console.log(`Dimensions changed to ${width}/${height}`);
+ setTimeout(measureWithHandler, 100);
+ };
+ const listener = react_native_1.Dimensions.addEventListener('change', handler);
+ return () => listener.remove();
+ }, [measureWithHandler]);
+ // Register and unregister externally when necessary.
+ (0, react_1.useEffect)(() => {
+ if (registration) { // Register externally when registration is set.
+ registration({
+ id,
+ measure: measureWithHandler,
+ });
+ return () => registration(undefined); // Unregister when we unmount or registration changes.
+ }
+ return undefined;
+ }, [id, registration, measureWithHandler]);
+ // Get the render-related state for rendering.
+ const viewState = getViewState(id);
+ const trackingStatus = getTrackingStatus();
+ // Get full render props for non-hovering view content.
+ const getRenderContentProps = (0, react_1.useCallback)(() => {
+ const measurements = measurementsRef.current;
+ const dimensions = measurements && (0, math_1.extractDimensions)(measurements);
+ return {
+ viewState,
+ trackingStatus,
+ children,
+ dimensions,
+ hover: false,
+ };
+ }, [
+ viewState,
+ trackingStatus,
+ children,
+ ]);
+ // Combined style for current render-related state.
+ const combinedStyle = (0, react_1.useMemo)(() => {
+ const { dragStatus = types_1.DraxViewDragStatus.Inactive, receiveStatus = types_1.DraxViewReceiveStatus.Inactive, } = viewState ?? {};
+ const { dragging: anyDragging, receiving: anyReceiving, } = trackingStatus;
+ // Start with base style.
+ const styles = [style];
+ // Apply style overrides for drag state.
+ if (dragStatus === types_1.DraxViewDragStatus.Dragging) {
+ styles.push(draggingStyle);
+ if (anyReceiving) {
+ styles.push(draggingWithReceiverStyle);
+ }
+ else {
+ styles.push(draggingWithoutReceiverStyle);
+ }
+ }
+ else if (dragStatus === types_1.DraxViewDragStatus.Released) {
+ styles.push(dragReleasedStyle);
+ }
+ else {
+ styles.push(dragInactiveStyle);
+ if (anyDragging) {
+ styles.push(otherDraggingStyle);
+ if (anyReceiving) {
+ styles.push(otherDraggingWithReceiverStyle);
+ }
+ else {
+ styles.push(otherDraggingWithoutReceiverStyle);
+ }
+ }
+ }
+ // Apply style overrides for receiving state.
+ if (receiveStatus === types_1.DraxViewReceiveStatus.Receiving) {
+ styles.push(receivingStyle);
+ }
+ else {
+ styles.push(receiverInactiveStyle);
+ }
+ return react_native_1.StyleSheet.flatten(styles);
+ }, [
+ viewState,
+ trackingStatus,
+ style,
+ dragInactiveStyle,
+ draggingStyle,
+ draggingWithReceiverStyle,
+ draggingWithoutReceiverStyle,
+ dragReleasedStyle,
+ receivingStyle,
+ receiverInactiveStyle,
+ otherDraggingStyle,
+ otherDraggingWithReceiverStyle,
+ otherDraggingWithoutReceiverStyle,
+ ]);
+ // The rendered React children of this view.
+ const renderedChildren = (0, react_1.useMemo)(() => {
+ let content;
+ if (renderContent) {
+ const renderContentProps = getRenderContentProps();
+ content = renderContent(renderContentProps);
+ }
+ else {
+ content = children;
+ }
+ if (isParent) {
+ // This is a Drax parent, so wrap children in subprovider.
+ content = (react_1.default.createElement(DraxSubprovider_1.DraxSubprovider, { parent: { id, nodeHandleRef } }, content));
+ }
+ return content;
+ }, [
+ renderContent,
+ getRenderContentProps,
+ children,
+ isParent,
+ id,
+ nodeHandleRef,
+ ]);
+ const setViewRefs = (0, react_1.useCallback)((ref) => {
+ if (inputViewRef) {
+ if (typeof (inputViewRef) === 'function') {
+ inputViewRef(ref);
+ }
+ else {
+ inputViewRef.current = ref;
+ }
+ }
+ viewRef.current = ref;
+ nodeHandleRef.current = ref && (0, react_native_1.findNodeHandle)(ref);
+ }, []);
+ return (react_1.default.createElement(react_native_gesture_handler_1.LongPressGestureHandler, { maxDist: Number.MAX_SAFE_INTEGER, shouldCancelWhenOutside: false, minDurationMs: longPressDelay, onHandlerStateChange: onHandlerStateChange, onGestureEvent: onGestureEvent /* Workaround incorrect typings. */, enabled: draggable },
+ react_1.default.createElement(react_native_1.Animated.View, { ...props, style: combinedStyle, ref: setViewRefs, onLayout: onLayout, collapsable: false }, renderedChildren)));
+};
+exports.DraxView = DraxView;
diff --git a/build/hooks/index.d.ts b/build/hooks/index.d.ts
new file mode 100644
index 0000000..fbdaaf9
--- /dev/null
+++ b/build/hooks/index.d.ts
@@ -0,0 +1,4 @@
+export { useDraxContext } from './useDraxContext';
+export { useDraxId } from './useDraxId';
+export { useDraxRegistry } from './useDraxRegistry';
+export { useDraxState } from './useDraxState';
diff --git a/build/hooks/index.js b/build/hooks/index.js
new file mode 100644
index 0000000..c957546
--- /dev/null
+++ b/build/hooks/index.js
@@ -0,0 +1,11 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.useDraxState = exports.useDraxRegistry = exports.useDraxId = exports.useDraxContext = void 0;
+var useDraxContext_1 = require("./useDraxContext");
+Object.defineProperty(exports, "useDraxContext", { enumerable: true, get: function () { return useDraxContext_1.useDraxContext; } });
+var useDraxId_1 = require("./useDraxId");
+Object.defineProperty(exports, "useDraxId", { enumerable: true, get: function () { return useDraxId_1.useDraxId; } });
+var useDraxRegistry_1 = require("./useDraxRegistry");
+Object.defineProperty(exports, "useDraxRegistry", { enumerable: true, get: function () { return useDraxRegistry_1.useDraxRegistry; } });
+var useDraxState_1 = require("./useDraxState");
+Object.defineProperty(exports, "useDraxState", { enumerable: true, get: function () { return useDraxState_1.useDraxState; } });
diff --git a/build/hooks/useDraxContext.d.ts b/build/hooks/useDraxContext.d.ts
new file mode 100644
index 0000000..f03b263
--- /dev/null
+++ b/build/hooks/useDraxContext.d.ts
@@ -0,0 +1 @@
+export declare const useDraxContext: () => import("..").DraxContextValue;
diff --git a/build/hooks/useDraxContext.js b/build/hooks/useDraxContext.js
new file mode 100644
index 0000000..744429f
--- /dev/null
+++ b/build/hooks/useDraxContext.js
@@ -0,0 +1,13 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.useDraxContext = void 0;
+const react_1 = require("react");
+const DraxContext_1 = require("../DraxContext");
+const useDraxContext = () => {
+ const drax = (0, react_1.useContext)(DraxContext_1.DraxContext);
+ if (!drax) {
+ throw Error('No DraxProvider found');
+ }
+ return drax;
+};
+exports.useDraxContext = useDraxContext;
diff --git a/build/hooks/useDraxId.d.ts b/build/hooks/useDraxId.d.ts
new file mode 100644
index 0000000..87afaf7
--- /dev/null
+++ b/build/hooks/useDraxId.d.ts
@@ -0,0 +1 @@
+export declare const useDraxId: (explicitId?: string) => string;
diff --git a/build/hooks/useDraxId.js b/build/hooks/useDraxId.js
new file mode 100644
index 0000000..d1cd282
--- /dev/null
+++ b/build/hooks/useDraxId.js
@@ -0,0 +1,13 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.useDraxId = void 0;
+const react_1 = require("react");
+const math_1 = require("../math");
+// Return explicitId, or a consistent randomly generated identifier if explicitId is falsy.
+const useDraxId = (explicitId) => {
+ // A generated unique identifier for this view, for use if id prop is not specified.
+ const [randomId] = (0, react_1.useState)(math_1.generateRandomId);
+ // We use || rather than ?? for the return value in case explicitId is an empty string.
+ return explicitId || randomId;
+};
+exports.useDraxId = useDraxId;
diff --git a/build/hooks/useDraxRegistry.d.ts b/build/hooks/useDraxRegistry.d.ts
new file mode 100644
index 0000000..c2ff48e
--- /dev/null
+++ b/build/hooks/useDraxRegistry.d.ts
@@ -0,0 +1,77 @@
+///
+import { Animated } from 'react-native';
+import { RegisterViewPayload, UnregisterViewPayload, UpdateViewProtocolPayload, UpdateViewMeasurementsPayload, DraxViewData, DraxViewMeasurements, Position, DraxFoundAbsoluteViewEntry, StartDragPayload, DraxAbsoluteViewEntry, DraxAbsoluteViewData, DraxStateDispatch, DraxSnapbackTarget } from '../types';
+interface GetDragPositionDataParams {
+ parentPosition: Position;
+ draggedMeasurements: DraxViewMeasurements;
+ lockXPosition?: boolean;
+ lockYPosition?: boolean;
+}
+/** Create a Drax registry and wire up all of the methods. */
+export declare const useDraxRegistry: (stateDispatch: DraxStateDispatch) => {
+ getViewData: (id: string | undefined) => DraxViewData | undefined;
+ getAbsoluteViewData: (id: string | undefined) => DraxAbsoluteViewData | undefined;
+ getTrackingDragged: () => {
+ tracking: import("../types").DraxTrackingDrag;
+ id: string;
+ data: DraxAbsoluteViewData;
+ } | undefined;
+ getTrackingReceiver: () => {
+ tracking: import("../types").DraxTrackingReceiver;
+ id: string;
+ data: DraxAbsoluteViewData;
+ } | undefined;
+ getTrackingMonitorIds: () => string[];
+ getTrackingMonitors: () => DraxAbsoluteViewEntry[];
+ getDragPositionData: (params: GetDragPositionDataParams) => {
+ dragAbsolutePosition: {
+ x: number;
+ y: number;
+ };
+ dragTranslation: {
+ x: number;
+ y: number;
+ };
+ dragTranslationRatio: {
+ x: number;
+ y: number;
+ };
+ } | undefined;
+ findMonitorsAndReceiver: (absolutePosition: Position, excludeViewId: string) => {
+ monitors: DraxFoundAbsoluteViewEntry[];
+ receiver: DraxFoundAbsoluteViewEntry;
+ };
+ getHoverItems: () => {
+ internalRenderHoverView: (props: import("../types").DraxInternalRenderHoverViewProps) => import("react").ReactNode;
+ key: string;
+ id: string;
+ hoverPosition: Animated.ValueXY;
+ dimensions: {
+ width: number;
+ height: number;
+ };
+ }[];
+ registerView: (payload: RegisterViewPayload) => void;
+ updateViewProtocol: (payload: UpdateViewProtocolPayload) => void;
+ updateViewMeasurements: (payload: UpdateViewMeasurementsPayload) => void;
+ resetReceiver: () => void;
+ resetDrag: (snapbackTarget?: DraxSnapbackTarget) => void;
+ startDrag: (payload: StartDragPayload) => {
+ dragAbsolutePosition: Position;
+ dragTranslation: {
+ x: number;
+ y: number;
+ };
+ dragTranslationRatio: {
+ x: number;
+ y: number;
+ };
+ dragOffset: Position;
+ hoverPosition: Animated.ValueXY;
+ };
+ updateDragPosition: (dragAbsolutePosition: Position) => void;
+ updateReceiver: (receiver: DraxFoundAbsoluteViewEntry, dragged: DraxAbsoluteViewEntry) => import("../types").DraxTrackingReceiver | undefined;
+ setMonitorIds: (monitorIds: string[]) => void;
+ unregisterView: (payload: UnregisterViewPayload) => void;
+};
+export {};
diff --git a/build/hooks/useDraxRegistry.js b/build/hooks/useDraxRegistry.js
new file mode 100644
index 0000000..ffe6fc2
--- /dev/null
+++ b/build/hooks/useDraxRegistry.js
@@ -0,0 +1,712 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.useDraxRegistry = void 0;
+const react_1 = require("react");
+const react_native_1 = require("react-native");
+const useDraxState_1 = require("./useDraxState");
+const types_1 = require("../types");
+const math_1 = require("../math");
+const params_1 = require("../params");
+/*
+ * The registry functions mutate their registry parameter, so let's
+ * disable the "no parameter reassignment" rule for the entire file:
+ */
+/* eslint-disable no-param-reassign */
+/** Create an initial empty Drax registry. */
+const createInitialRegistry = (stateDispatch) => ({
+ stateDispatch,
+ viewIds: [],
+ viewDataById: {},
+ drag: undefined,
+ releaseIds: [],
+ releaseById: {},
+});
+/** Create the initial empty protocol data for a newly registered view. */
+const createInitialProtocol = () => ({
+ draggable: false,
+ receptive: false,
+ monitoring: false,
+});
+/** Get data for a registered view by its id. */
+const getViewDataFromRegistry = (registry, id) => ((id && registry.viewIds.includes(id)) ? registry.viewDataById[id] : undefined);
+/** Get absolute measurements for a registered view, incorporating parents and clipping. */
+const getAbsoluteMeasurementsForViewFromRegistry = (registry, { measurements, parentId }, clipped = false) => {
+ if (!measurements) {
+ // console.log('Failed to get absolute measurements for view: no measurements');
+ return undefined;
+ }
+ if (!parentId) {
+ return measurements;
+ }
+ const parentViewData = getViewDataFromRegistry(registry, parentId);
+ if (!parentViewData) {
+ // console.log(`Failed to get absolute measurements for view: no view data for parent id ${parentId}`);
+ return undefined;
+ }
+ const parentMeasurements = getAbsoluteMeasurementsForViewFromRegistry(registry, parentViewData, clipped);
+ if (!parentMeasurements) {
+ // console.log(`Failed to get absolute measurements for view: no absolute measurements for parent id ${parentId}`);
+ return undefined;
+ }
+ const { x, y, width, height, } = measurements;
+ const { x: parentX, y: parentY, } = parentMeasurements;
+ const { x: offsetX, y: offsetY } = parentViewData.scrollPositionRef?.current || { x: 0, y: 0 };
+ const abs = {
+ width,
+ height,
+ x: parentX + x - offsetX,
+ y: parentY + y - offsetY,
+ };
+ return clipped ? (0, math_1.clipMeasurements)(abs, parentMeasurements) : abs;
+};
+/** Get data, including absolute measurements, for a registered view by its id. */
+const getAbsoluteViewDataFromRegistry = (registry, id) => {
+ const viewData = getViewDataFromRegistry(registry, id);
+ if (!viewData) {
+ // console.log(`No view data for id ${id}`);
+ return undefined;
+ }
+ const absoluteMeasurements = getAbsoluteMeasurementsForViewFromRegistry(registry, viewData);
+ if (!absoluteMeasurements) {
+ // console.log(`No absolute measurements for id ${id}`);
+ return undefined;
+ }
+ return {
+ ...viewData,
+ measurements: viewData.measurements,
+ absoluteMeasurements,
+ };
+};
+/** Convenience function to return a view's id and absolute data. */
+const getAbsoluteViewEntryFromRegistry = (registry, id) => {
+ if (id === undefined) {
+ return undefined;
+ }
+ const data = getAbsoluteViewDataFromRegistry(registry, id);
+ return data && { id, data };
+};
+/**
+ * If multiple recievers match, we need to pick the one that is on top. This
+ * is first done by filtering out all that are parents (because parent views are below child ones)
+ * and then if there are any further possibilities, it chooses the smallest one.
+ */
+const getTopMostReceiver = (receivers) => {
+ const ids = receivers.map(receiver => receiver.data.parentId);
+ receivers = receivers.filter(receiver => !ids.includes(receiver.id));
+ receivers.sort((receiverA, receiverB) => receiverA.data.measurements.height * receiverA.data.measurements.width - receiverB.data.measurements.height * receiverB.data.measurements.width);
+ return receivers[0];
+};
+/**
+ * Find all monitoring views and the latest receptive view that
+ * contain the touch coordinates, excluding the specified view.
+ */
+const findMonitorsAndReceiverInRegistry = (registry, absolutePosition, excludeViewId) => {
+ const monitors = [];
+ let receivers = [];
+ // console.log(`find monitors and receiver for absolute position (${absolutePosition.x}, ${absolutePosition.y})`);
+ registry.viewIds.forEach((targetId) => {
+ // console.log(`checking target id ${targetId}`);
+ if (targetId === excludeViewId) {
+ // Don't consider the excluded view.
+ // console.log('excluded');
+ return;
+ }
+ const target = getViewDataFromRegistry(registry, targetId);
+ if (!target) {
+ // This should never happen, but just in case.
+ // console.log('no view data found');
+ return;
+ }
+ const { receptive, monitoring } = target.protocol;
+ if (!receptive && !monitoring) {
+ // Only consider receptive or monitoring views.
+ // console.log('not receptive nor monitoring');
+ return;
+ }
+ const absoluteMeasurements = getAbsoluteMeasurementsForViewFromRegistry(registry, target, true);
+ if (!absoluteMeasurements) {
+ // Only consider views for which we have absolute measurements.
+ // console.log('failed to find absolute measurements');
+ return;
+ }
+ // console.log(`absolute measurements: ${JSON.stringify(absoluteMeasurements, null, 2)}`);
+ if ((0, math_1.isPointInside)(absolutePosition, absoluteMeasurements)) {
+ // Drag point is within this target.
+ const foundView = {
+ id: targetId,
+ data: {
+ ...target,
+ measurements: target.measurements,
+ absoluteMeasurements,
+ },
+ ...(0, math_1.getRelativePosition)(absolutePosition, absoluteMeasurements),
+ };
+ if (monitoring) {
+ // Add it to the list of monitors.
+ monitors.push(foundView);
+ // console.log('it\'s a monitor');
+ }
+ if (receptive) {
+ // It's the latest receiver found.
+ receivers.push(foundView);
+ // console.log('it\'s a receiver');
+ }
+ }
+ });
+ return {
+ monitors,
+ receiver: getTopMostReceiver(receivers)
+ };
+};
+/** Get id and data for the currently dragged view, if any. */
+const getTrackingDraggedFromRegistry = (registry) => {
+ const tracking = registry.drag;
+ if (tracking !== undefined) {
+ const viewEntry = getAbsoluteViewEntryFromRegistry(registry, tracking.draggedId);
+ if (viewEntry !== undefined) {
+ return {
+ ...viewEntry,
+ tracking,
+ };
+ }
+ }
+ return undefined;
+};
+/** Get id and data for the currently receiving view, if any. */
+const getTrackingReceiverFromRegistry = (registry) => {
+ const tracking = registry.drag?.receiver;
+ if (tracking !== undefined) {
+ const viewEntry = getAbsoluteViewEntryFromRegistry(registry, tracking.receiverId);
+ if (viewEntry !== undefined) {
+ return {
+ ...viewEntry,
+ tracking,
+ };
+ }
+ }
+ return undefined;
+};
+/** Get ids for all currently monitoring views. */
+const getTrackingMonitorIdsFromRegistry = (registry) => (registry.drag?.monitorIds || []);
+/** Get id and data for all currently monitoring views. */
+const getTrackingMonitorsFromRegistry = (registry) => (registry.drag?.monitorIds
+ .map((id) => getAbsoluteViewEntryFromRegistry(registry, id))
+ .filter((value) => !!value)
+ || []);
+/** Get the array of hover items for dragged and released views */
+const getHoverItemsFromRegistry = (registry) => {
+ const hoverItems = [];
+ // Find all released view hover items, in order from oldest to newest.
+ registry.releaseIds.forEach((releaseId) => {
+ const release = registry.releaseById[releaseId];
+ if (release) {
+ const { viewId, hoverPosition } = release;
+ const releasedData = getAbsoluteViewDataFromRegistry(registry, viewId);
+ if (releasedData) {
+ const { protocol: { internalRenderHoverView }, measurements } = releasedData;
+ if (internalRenderHoverView) {
+ hoverItems.push({
+ hoverPosition,
+ internalRenderHoverView,
+ key: releaseId,
+ id: viewId,
+ dimensions: (0, math_1.extractDimensions)(measurements),
+ });
+ }
+ }
+ }
+ });
+ // Find the currently dragged hover item.
+ const { id: draggedId, data: draggedData } = getTrackingDraggedFromRegistry(registry) ?? {};
+ if (draggedData) {
+ const { protocol: { internalRenderHoverView }, measurements } = draggedData;
+ if (draggedId && internalRenderHoverView) {
+ hoverItems.push({
+ internalRenderHoverView,
+ key: `dragged-hover-${draggedId}`,
+ id: draggedId,
+ hoverPosition: registry.drag.hoverPosition,
+ dimensions: (0, math_1.extractDimensions)(measurements),
+ });
+ }
+ }
+ return hoverItems;
+};
+/**
+ * Get the absolute position of a drag already in progress from touch
+ * coordinates within the immediate parent view of the dragged view.
+ */
+const getDragPositionDataFromRegistry = (registry, { parentPosition, draggedMeasurements, lockXPosition = false, lockYPosition = false, }) => {
+ if (!registry.drag) {
+ return undefined;
+ }
+ /*
+ * To determine drag position in absolute coordinates, we add:
+ * absolute coordinates of drag start
+ * + translation offset of drag
+ */
+ const { absoluteStartPosition, parentStartPosition, } = registry.drag;
+ const dragTranslation = {
+ x: lockXPosition ? 0 : (parentPosition.x - parentStartPosition.x),
+ y: lockYPosition ? 0 : (parentPosition.y - parentStartPosition.y),
+ };
+ const dragTranslationRatio = {
+ x: dragTranslation.x / draggedMeasurements.width,
+ y: dragTranslation.y / draggedMeasurements.height,
+ };
+ const dragAbsolutePosition = {
+ x: absoluteStartPosition.x + dragTranslation.x,
+ y: absoluteStartPosition.y + dragTranslation.y,
+ };
+ return {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragTranslationRatio,
+ };
+};
+/** Register a Drax view. */
+const registerViewInRegistry = (registry, { id, parentId, scrollPositionRef }) => {
+ const { viewIds, viewDataById, stateDispatch } = registry;
+ // Make sure not to duplicate registered view id.
+ if (viewIds.indexOf(id) < 0) {
+ viewIds.push(id);
+ }
+ // Maintain any existing view data.
+ const existingData = getViewDataFromRegistry(registry, id);
+ // console.log(`Register view ${id} with parent ${parentId}`);
+ viewDataById[id] = {
+ parentId,
+ scrollPositionRef,
+ protocol: existingData?.protocol ?? createInitialProtocol(),
+ measurements: existingData?.measurements, // Starts undefined.
+ };
+ stateDispatch(useDraxState_1.actions.createViewState({ id }));
+};
+/** Update a view's protocol callbacks/data. */
+const updateViewProtocolInRegistry = (registry, { id, protocol }) => {
+ const existingData = getViewDataFromRegistry(registry, id);
+ if (existingData) {
+ registry.viewDataById[id].protocol = protocol;
+ }
+};
+/** Update a view's measurements. */
+const updateViewMeasurementsInRegistry = (registry, { id, measurements }) => {
+ const existingData = getViewDataFromRegistry(registry, id);
+ if (existingData) {
+ // console.log(`Update ${id} measurements: @(${measurements?.x}, ${measurements?.y}) ${measurements?.width}x${measurements?.height}`);
+ registry.viewDataById[id].measurements = measurements;
+ }
+};
+/** Reset the receiver in drag tracking, if any. */
+const resetReceiverInRegistry = ({ drag, stateDispatch }) => {
+ if (!drag) {
+ return;
+ }
+ const { draggedId, receiver } = drag;
+ if (!receiver) {
+ // console.log('no receiver to clear');
+ return;
+ }
+ // console.log('clearing receiver');
+ drag.receiver = undefined;
+ stateDispatch(useDraxState_1.actions.updateTrackingStatus({ receiving: false }));
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: draggedId,
+ viewStateUpdate: {
+ draggingOverReceiver: undefined,
+ },
+ }));
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: receiver.receiverId,
+ viewStateUpdate: {
+ receiveStatus: types_1.DraxViewReceiveStatus.Inactive,
+ receiveOffset: undefined,
+ receiveOffsetRatio: undefined,
+ receivingDrag: undefined,
+ },
+ }));
+};
+/** Track a new release, returning its unique identifier. */
+const createReleaseInRegistry = (registry, release) => {
+ const releaseId = (0, math_1.generateRandomId)();
+ registry.releaseIds.push(releaseId);
+ registry.releaseById[releaseId] = release;
+ return releaseId;
+};
+/** Stop tracking a release, given its unique identifier. */
+const deleteReleaseInRegistry = (registry, releaseId) => {
+ registry.releaseIds = registry.releaseIds.filter((id) => id !== releaseId);
+ delete registry.releaseById[releaseId];
+};
+/** Reset drag tracking, if any. */
+const resetDragInRegistry = (registry, snapbackTarget = types_1.DraxSnapbackTargetPreset.Default) => {
+ const { drag, stateDispatch } = registry;
+ if (!drag) {
+ return;
+ }
+ resetReceiverInRegistry(registry);
+ const { draggedId, hoverPosition } = drag;
+ const draggedData = getAbsoluteViewDataFromRegistry(registry, draggedId);
+ // Clear the drag.
+ // console.log('clearing drag');
+ registry.drag = undefined;
+ // Determine if/where/how to snapback.
+ let snapping = false;
+ if (snapbackTarget !== types_1.DraxSnapbackTargetPreset.None && draggedData) {
+ const { internalRenderHoverView, onSnapbackEnd, snapbackAnimator, animateSnapback = true, snapbackDelay = params_1.defaultSnapbackDelay, snapbackDuration = params_1.defaultSnapbackDuration, } = draggedData.protocol;
+ if (internalRenderHoverView && animateSnapback) {
+ let toValue;
+ if ((0, types_1.isPosition)(snapbackTarget)) {
+ // Snapback to specified target.
+ toValue = snapbackTarget;
+ }
+ else {
+ // Snapback to default position (where original view is).
+ toValue = {
+ x: draggedData.absoluteMeasurements.x,
+ y: draggedData.absoluteMeasurements.y,
+ };
+ }
+ if (toValue && snapbackDuration > 0) {
+ snapping = true;
+ // Add a release to tracking.
+ const releaseId = createReleaseInRegistry(registry, { hoverPosition, viewId: draggedId });
+ // Animate the released hover snapback.
+ let animation;
+ if (snapbackAnimator) {
+ animation = snapbackAnimator({
+ hoverPosition,
+ toValue,
+ delay: snapbackDelay,
+ duration: snapbackDuration,
+ });
+ }
+ else {
+ animation = react_native_1.Animated.timing(hoverPosition, {
+ toValue,
+ delay: snapbackDelay,
+ duration: snapbackDuration,
+ useNativeDriver: true,
+ });
+ }
+ animation.start(({ finished }) => {
+ // Remove the release from tracking, regardless of whether animation finished.
+ deleteReleaseInRegistry(registry, releaseId);
+ // Call the snapback end handler, regardless of whether animation of finished.
+ onSnapbackEnd?.();
+ // If the animation finished, update the view state for the released view to be inactive.
+ if (finished) {
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: draggedId,
+ viewStateUpdate: {
+ dragStatus: types_1.DraxViewDragStatus.Inactive,
+ hoverPosition: undefined,
+ grabOffset: undefined,
+ grabOffsetRatio: undefined,
+ },
+ }));
+ }
+ });
+ }
+ }
+ }
+ // Update the drag tracking status.
+ stateDispatch(useDraxState_1.actions.updateTrackingStatus({ dragging: false }));
+ // Update the view state, data dependent on whether snapping back.
+ const viewStateUpdate = {
+ dragAbsolutePosition: undefined,
+ dragTranslation: undefined,
+ dragTranslationRatio: undefined,
+ dragOffset: undefined,
+ };
+ if (snapping) {
+ viewStateUpdate.dragStatus = types_1.DraxViewDragStatus.Released;
+ }
+ else {
+ viewStateUpdate.dragStatus = types_1.DraxViewDragStatus.Inactive;
+ viewStateUpdate.hoverPosition = undefined;
+ viewStateUpdate.grabOffset = undefined;
+ viewStateUpdate.grabOffsetRatio = undefined;
+ }
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ viewStateUpdate,
+ id: draggedId,
+ }));
+};
+/** Start tracking a drag. */
+const startDragInRegistry = (registry, { dragAbsolutePosition, dragParentPosition, draggedId, grabOffset, grabOffsetRatio, }) => {
+ const { stateDispatch } = registry;
+ resetDragInRegistry(registry);
+ const dragTranslation = { x: 0, y: 0 };
+ const dragTranslationRatio = { x: 0, y: 0 };
+ const dragOffset = grabOffset;
+ const hoverPosition = new react_native_1.Animated.ValueXY({
+ x: dragAbsolutePosition.x - grabOffset.x,
+ y: dragAbsolutePosition.y - grabOffset.y,
+ });
+ registry.drag = {
+ absoluteStartPosition: dragAbsolutePosition,
+ parentStartPosition: dragParentPosition,
+ draggedId,
+ dragAbsolutePosition,
+ dragTranslation,
+ dragTranslationRatio,
+ dragOffset,
+ grabOffset,
+ grabOffsetRatio,
+ hoverPosition,
+ receiver: undefined,
+ monitorIds: [],
+ };
+ stateDispatch(useDraxState_1.actions.updateTrackingStatus({ dragging: true }));
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: draggedId,
+ viewStateUpdate: {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragTranslationRatio,
+ dragOffset,
+ grabOffset,
+ grabOffsetRatio,
+ hoverPosition,
+ dragStatus: types_1.DraxViewDragStatus.Dragging,
+ },
+ }));
+ return {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragTranslationRatio,
+ dragOffset,
+ hoverPosition,
+ };
+};
+/** Update drag position. */
+const updateDragPositionInRegistry = (registry, dragAbsolutePosition) => {
+ const { drag, stateDispatch } = registry;
+ if (!drag) {
+ return;
+ }
+ const dragged = getTrackingDraggedFromRegistry(registry);
+ if (!dragged) {
+ return;
+ }
+ const { absoluteMeasurements } = dragged.data;
+ const { draggedId, grabOffset, hoverPosition } = drag;
+ const dragTranslation = {
+ x: dragAbsolutePosition.x - drag.absoluteStartPosition.x,
+ y: dragAbsolutePosition.y - drag.absoluteStartPosition.y,
+ };
+ const dragTranslationRatio = {
+ x: dragTranslation.x / absoluteMeasurements.width,
+ y: dragTranslation.y / absoluteMeasurements.height,
+ };
+ const dragOffset = {
+ x: dragAbsolutePosition.x - absoluteMeasurements.x,
+ y: dragAbsolutePosition.y - absoluteMeasurements.y,
+ };
+ drag.dragAbsolutePosition = dragAbsolutePosition;
+ drag.dragTranslation = dragTranslation;
+ drag.dragTranslationRatio = dragTranslationRatio;
+ drag.dragOffset = dragOffset;
+ hoverPosition.setValue({
+ x: dragAbsolutePosition.x - grabOffset.x,
+ y: dragAbsolutePosition.y - grabOffset.y,
+ });
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: draggedId,
+ viewStateUpdate: {
+ dragAbsolutePosition,
+ dragTranslation,
+ dragTranslationRatio,
+ dragOffset,
+ },
+ }));
+};
+/** Update receiver for a drag. */
+const updateReceiverInRegistry = (registry, receiver, dragged) => {
+ const { drag, stateDispatch } = registry;
+ if (!drag) {
+ return undefined;
+ }
+ const { relativePosition, relativePositionRatio, id: receiverId, data: receiverData, } = receiver;
+ const { parentId: receiverParentId, protocol: { receiverPayload }, } = receiverData;
+ const { id: draggedId, data: draggedData, } = dragged;
+ const { parentId: draggedParentId, protocol: { dragPayload }, } = draggedData;
+ const oldReceiver = drag.receiver;
+ const receiveOffset = relativePosition;
+ const receiveOffsetRatio = relativePositionRatio;
+ const receiverUpdate = {
+ receivingDrag: {
+ id: draggedId,
+ parentId: draggedParentId,
+ payload: dragPayload,
+ },
+ receiveOffset,
+ receiveOffsetRatio,
+ };
+ if (oldReceiver?.receiverId === receiverId) {
+ // Same receiver, update offsets.
+ oldReceiver.receiveOffset = receiveOffset;
+ oldReceiver.receiveOffsetRatio = receiveOffsetRatio;
+ }
+ else {
+ // New receiver.
+ if (oldReceiver) {
+ // Clear the old receiver.
+ resetReceiverInRegistry(registry);
+ }
+ drag.receiver = {
+ receiverId,
+ receiveOffset,
+ receiveOffsetRatio,
+ };
+ receiverUpdate.receiveStatus = types_1.DraxViewReceiveStatus.Receiving;
+ stateDispatch(useDraxState_1.actions.updateTrackingStatus({ receiving: true }));
+ }
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: receiverId,
+ viewStateUpdate: receiverUpdate,
+ }));
+ stateDispatch(useDraxState_1.actions.updateViewState({
+ id: draggedId,
+ viewStateUpdate: {
+ draggingOverReceiver: {
+ id: receiverId,
+ parentId: receiverParentId,
+ payload: receiverPayload,
+ },
+ },
+ }));
+ return drag.receiver;
+};
+/** Set the monitors for a drag. */
+const setMonitorIdsInRegistry = ({ drag }, monitorIds) => {
+ if (drag) {
+ drag.monitorIds = monitorIds;
+ }
+};
+/** Unregister a Drax view. */
+const unregisterViewInRegistry = (registry, { id }) => {
+ const { [id]: removed, ...viewDataById } = registry.viewDataById;
+ registry.viewIds = registry.viewIds.filter((thisId) => thisId !== id);
+ registry.viewDataById = viewDataById;
+ if (registry.drag?.draggedId === id) {
+ resetDragInRegistry(registry);
+ }
+ else if (registry.drag?.receiver?.receiverId === id) {
+ resetReceiverInRegistry(registry);
+ }
+ registry.stateDispatch(useDraxState_1.actions.deleteViewState({ id }));
+};
+/** Create a Drax registry and wire up all of the methods. */
+const useDraxRegistry = (stateDispatch) => {
+ /** Registry for tracking views and drags. */
+ const registryRef = (0, react_1.useRef)(createInitialRegistry(stateDispatch));
+ /** Ensure that the registry has the latest version of state dispatch, although it should never change. */
+ (0, react_1.useEffect)(() => {
+ registryRef.current.stateDispatch = stateDispatch;
+ }, [stateDispatch]);
+ /**
+ *
+ * Getters/finders, with no state reactions.
+ *
+ */
+ /** Get data for a registered view by its id. */
+ const getViewData = (0, react_1.useCallback)((id) => getViewDataFromRegistry(registryRef.current, id), []);
+ /** Get data, including absolute measurements, for a registered view by its id. */
+ const getAbsoluteViewData = (0, react_1.useCallback)((id) => getAbsoluteViewDataFromRegistry(registryRef.current, id), []);
+ /** Get id and data for the currently dragged view, if any. */
+ const getTrackingDragged = (0, react_1.useCallback)(() => getTrackingDraggedFromRegistry(registryRef.current), []);
+ /** Get id and data for the currently receiving view, if any. */
+ const getTrackingReceiver = (0, react_1.useCallback)(() => getTrackingReceiverFromRegistry(registryRef.current), []);
+ /** Get ids for all currently monitoring views. */
+ const getTrackingMonitorIds = (0, react_1.useCallback)(() => getTrackingMonitorIdsFromRegistry(registryRef.current), []);
+ /** Get id and data for all currently monitoring views. */
+ const getTrackingMonitors = (0, react_1.useCallback)(() => getTrackingMonitorsFromRegistry(registryRef.current), []);
+ /**
+ * Get the absolute position of a drag already in progress from touch
+ * coordinates within the immediate parent view of the dragged view.
+ */
+ const getDragPositionData = (0, react_1.useCallback)((params) => (getDragPositionDataFromRegistry(registryRef.current, params)), []);
+ /**
+ * Find all monitoring views and the latest receptive view that
+ * contain the touch coordinates, excluding the specified view.
+ */
+ const findMonitorsAndReceiver = (0, react_1.useCallback)((absolutePosition, excludeViewId) => (findMonitorsAndReceiverInRegistry(registryRef.current, absolutePosition, excludeViewId)), []);
+ /** Get the array of hover items for dragged and released views */
+ const getHoverItems = (0, react_1.useCallback)(() => getHoverItemsFromRegistry(registryRef.current), []);
+ /**
+ *
+ * Imperative methods without state reactions (data management only).
+ *
+ */
+ /** Update a view's protocol callbacks/data. */
+ const updateViewProtocol = (0, react_1.useCallback)((payload) => updateViewProtocolInRegistry(registryRef.current, payload), []);
+ /** Update a view's measurements. */
+ const updateViewMeasurements = (0, react_1.useCallback)((payload) => updateViewMeasurementsInRegistry(registryRef.current, payload), []);
+ /**
+ *
+ * Imperative methods with potential state reactions.
+ *
+ */
+ /** Register a Drax view. */
+ const registerView = (0, react_1.useCallback)((payload) => registerViewInRegistry(registryRef.current, payload), []);
+ /** Reset the receiver in drag tracking, if any. */
+ const resetReceiver = (0, react_1.useCallback)(() => resetReceiverInRegistry(registryRef.current), []);
+ /** Reset drag tracking, if any. */
+ const resetDrag = (0, react_1.useCallback)((snapbackTarget) => resetDragInRegistry(registryRef.current, snapbackTarget), []);
+ /** Start tracking a drag. */
+ const startDrag = (0, react_1.useCallback)((payload) => startDragInRegistry(registryRef.current, payload), []);
+ /** Update drag position. */
+ const updateDragPosition = (0, react_1.useCallback)((dragAbsolutePosition) => (updateDragPositionInRegistry(registryRef.current, dragAbsolutePosition)), []);
+ /** Update the receiver for a drag. */
+ const updateReceiver = (0, react_1.useCallback)((receiver, dragged) => (updateReceiverInRegistry(registryRef.current, receiver, dragged)), []);
+ /** Set the monitors for a drag. */
+ const setMonitorIds = (0, react_1.useCallback)((monitorIds) => setMonitorIdsInRegistry(registryRef.current, monitorIds), []);
+ /** Unregister a Drax view. */
+ const unregisterView = (0, react_1.useCallback)((payload) => unregisterViewInRegistry(registryRef.current, payload), []);
+ /** Create the Drax registry object for return, only replacing reference when necessary. */
+ const draxRegistry = (0, react_1.useMemo)(() => ({
+ getViewData,
+ getAbsoluteViewData,
+ getTrackingDragged,
+ getTrackingReceiver,
+ getTrackingMonitorIds,
+ getTrackingMonitors,
+ getDragPositionData,
+ findMonitorsAndReceiver,
+ getHoverItems,
+ registerView,
+ updateViewProtocol,
+ updateViewMeasurements,
+ resetReceiver,
+ resetDrag,
+ startDrag,
+ updateDragPosition,
+ updateReceiver,
+ setMonitorIds,
+ unregisterView,
+ }), [
+ getViewData,
+ getAbsoluteViewData,
+ getTrackingDragged,
+ getTrackingReceiver,
+ getTrackingMonitorIds,
+ getTrackingMonitors,
+ getDragPositionData,
+ findMonitorsAndReceiver,
+ getHoverItems,
+ registerView,
+ updateViewProtocol,
+ updateViewMeasurements,
+ resetReceiver,
+ resetDrag,
+ startDrag,
+ updateDragPosition,
+ updateReceiver,
+ setMonitorIds,
+ unregisterView,
+ ]);
+ return draxRegistry;
+};
+exports.useDraxRegistry = useDraxRegistry;
diff --git a/build/hooks/useDraxState.d.ts b/build/hooks/useDraxState.d.ts
new file mode 100644
index 0000000..e29fe89
--- /dev/null
+++ b/build/hooks/useDraxState.d.ts
@@ -0,0 +1,10 @@
+///
+import { DraxViewState, DraxStateActionCreators, CreateViewStatePayload, UpdateViewStatePayload, DeleteViewStatePayload, UpdateTrackingStatusPayload } from '../types';
+/** Collection of Drax action creators */
+export declare const actions: DraxStateActionCreators;
+/** Create a Drax state and wire up its methods. */
+export declare const useDraxState: () => {
+ getViewState: (id: string | undefined) => DraxViewState | undefined;
+ getTrackingStatus: () => import("../types").DraxTrackingStatus;
+ dispatch: import("react").Dispatch | import("typesafe-actions").PayloadAction<"updateViewState", UpdateViewStatePayload> | import("typesafe-actions").PayloadAction<"deleteViewState", DeleteViewStatePayload> | import("typesafe-actions").PayloadAction<"updateTrackingStatus", UpdateTrackingStatusPayload>>;
+};
diff --git a/build/hooks/useDraxState.js b/build/hooks/useDraxState.js
new file mode 100644
index 0000000..10a98e9
--- /dev/null
+++ b/build/hooks/useDraxState.js
@@ -0,0 +1,129 @@
+"use strict";
+var __importDefault = (this && this.__importDefault) || function (mod) {
+ return (mod && mod.__esModule) ? mod : { "default": mod };
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.useDraxState = exports.actions = void 0;
+const react_1 = require("react");
+const typesafe_actions_1 = require("typesafe-actions");
+const lodash_isequal_1 = __importDefault(require("lodash.isequal"));
+const types_1 = require("../types");
+/** Create the initial empty view state data for a newly registered view. */
+const createInitialViewState = () => ({
+ dragStatus: types_1.DraxViewDragStatus.Inactive,
+ dragAbsolutePosition: undefined,
+ dragOffset: undefined,
+ grabOffset: undefined,
+ grabOffsetRatio: undefined,
+ draggingOverReceiver: undefined,
+ receiveStatus: types_1.DraxViewReceiveStatus.Inactive,
+ receiveOffset: undefined,
+ receiveOffsetRatio: undefined,
+ receivingDrag: undefined,
+});
+/** Create an initial empty Drax state. */
+const createInitialState = () => ({
+ viewStateById: {},
+ trackingStatus: {
+ dragging: false,
+ receiving: false,
+ },
+});
+/** Selector for a view state by view id. */
+const selectViewState = (state, id) => (id === undefined ? undefined : state.viewStateById[id]);
+/** Selector for tracking status. */
+const selectTrackingStatus = (state) => state.trackingStatus;
+/** Collection of Drax action creators */
+exports.actions = {
+ createViewState: (0, typesafe_actions_1.createAction)('createViewState')(),
+ updateViewState: (0, typesafe_actions_1.createAction)('updateViewState')(),
+ deleteViewState: (0, typesafe_actions_1.createAction)('deleteViewState')(),
+ updateTrackingStatus: (0, typesafe_actions_1.createAction)('updateTrackingStatus')(),
+};
+/** The DraxState reducer. */
+const reducer = (state, action) => {
+ switch (action.type) {
+ case (0, typesafe_actions_1.getType)(exports.actions.createViewState): {
+ const { id } = action.payload;
+ const viewState = selectViewState(state, id);
+ if (viewState) {
+ return state;
+ }
+ return {
+ ...state,
+ viewStateById: {
+ ...state.viewStateById,
+ [id]: createInitialViewState(),
+ },
+ };
+ }
+ case (0, typesafe_actions_1.getType)(exports.actions.updateViewState): {
+ const { id, viewStateUpdate } = action.payload;
+ const viewState = selectViewState(state, id);
+ if (viewState) {
+ const newViewState = {
+ ...viewState,
+ ...viewStateUpdate,
+ };
+ if ((0, lodash_isequal_1.default)(viewState, newViewState)) {
+ return state;
+ }
+ return {
+ ...state,
+ viewStateById: {
+ ...state.viewStateById,
+ [id]: newViewState,
+ },
+ };
+ }
+ return state;
+ }
+ case (0, typesafe_actions_1.getType)(exports.actions.deleteViewState): {
+ const { id } = action.payload;
+ const { [id]: removed, ...viewStateById } = state.viewStateById;
+ if (removed) {
+ return {
+ ...state,
+ viewStateById,
+ };
+ }
+ return state;
+ }
+ case (0, typesafe_actions_1.getType)(exports.actions.updateTrackingStatus): {
+ return {
+ ...state,
+ trackingStatus: {
+ ...state.trackingStatus,
+ ...action.payload,
+ },
+ };
+ }
+ default:
+ return state;
+ }
+};
+/** Create a Drax state and wire up its methods. */
+const useDraxState = () => {
+ /** Reducer for storing view states and tracking status. */
+ const [state, dispatch] = (0, react_1.useReducer)(reducer, undefined, createInitialState);
+ /** Get state for a view by its id. */
+ const getViewState = (0, react_1.useCallback)((id) => selectViewState(state, id), [state]);
+ /** Get the current tracking status. */
+ const getTrackingStatus = (0, react_1.useCallback)(() => selectTrackingStatus(state), [state]);
+ /** Create the Drax state object for return, only replacing reference when necessary. */
+ const draxState = (0, react_1.useMemo)(() => ({
+ getViewState,
+ getTrackingStatus,
+ dispatch,
+ }), [
+ getViewState,
+ getTrackingStatus,
+ ]);
+ /*
+ useEffect(() => {
+ console.log(`Rendering drax state ${JSON.stringify(state, null, 2)}`);
+ });
+ */
+ return draxState;
+};
+exports.useDraxState = useDraxState;
diff --git a/build/index.d.ts b/build/index.d.ts
new file mode 100644
index 0000000..3f862fb
--- /dev/null
+++ b/build/index.d.ts
@@ -0,0 +1,7 @@
+export * from './types';
+export { DraxContext } from './DraxContext';
+export { DraxList } from './DraxList';
+export { DraxProvider } from './DraxProvider';
+export { DraxScrollView } from './DraxScrollView';
+export { DraxSubprovider } from './DraxSubprovider';
+export { DraxView } from './DraxView';
diff --git a/build/index.js b/build/index.js
new file mode 100644
index 0000000..f6c50d1
--- /dev/null
+++ b/build/index.js
@@ -0,0 +1,30 @@
+"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
+}) : (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ o[k2] = m[k];
+}));
+var __exportStar = (this && this.__exportStar) || function(m, exports) {
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.DraxView = exports.DraxSubprovider = exports.DraxScrollView = exports.DraxProvider = exports.DraxList = exports.DraxContext = void 0;
+__exportStar(require("./types"), exports);
+var DraxContext_1 = require("./DraxContext");
+Object.defineProperty(exports, "DraxContext", { enumerable: true, get: function () { return DraxContext_1.DraxContext; } });
+var DraxList_1 = require("./DraxList");
+Object.defineProperty(exports, "DraxList", { enumerable: true, get: function () { return DraxList_1.DraxList; } });
+var DraxProvider_1 = require("./DraxProvider");
+Object.defineProperty(exports, "DraxProvider", { enumerable: true, get: function () { return DraxProvider_1.DraxProvider; } });
+var DraxScrollView_1 = require("./DraxScrollView");
+Object.defineProperty(exports, "DraxScrollView", { enumerable: true, get: function () { return DraxScrollView_1.DraxScrollView; } });
+var DraxSubprovider_1 = require("./DraxSubprovider");
+Object.defineProperty(exports, "DraxSubprovider", { enumerable: true, get: function () { return DraxSubprovider_1.DraxSubprovider; } });
+var DraxView_1 = require("./DraxView");
+Object.defineProperty(exports, "DraxView", { enumerable: true, get: function () { return DraxView_1.DraxView; } });
diff --git a/build/math.d.ts b/build/math.d.ts
new file mode 100644
index 0000000..d6b34b9
--- /dev/null
+++ b/build/math.d.ts
@@ -0,0 +1,22 @@
+import { DraxViewMeasurements, Position } from './types';
+export declare const clipMeasurements: (vm: DraxViewMeasurements, cvm: DraxViewMeasurements) => DraxViewMeasurements;
+export declare const isPointInside: ({ x, y }: Position, { width, height, x: x0, y: y0, }: DraxViewMeasurements) => boolean;
+export declare const getRelativePosition: ({ x, y }: Position, { width, height, x: x0, y: y0, }: DraxViewMeasurements) => {
+ relativePosition: {
+ x: number;
+ y: number;
+ };
+ relativePositionRatio: {
+ x: number;
+ y: number;
+ };
+};
+export declare const extractPosition: ({ x, y }: DraxViewMeasurements) => {
+ x: number;
+ y: number;
+};
+export declare const extractDimensions: ({ width, height }: DraxViewMeasurements) => {
+ width: number;
+ height: number;
+};
+export declare const generateRandomId: () => string;
diff --git a/build/math.js b/build/math.js
new file mode 100644
index 0000000..c9242b0
--- /dev/null
+++ b/build/math.js
@@ -0,0 +1,64 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.generateRandomId = exports.extractDimensions = exports.extractPosition = exports.getRelativePosition = exports.isPointInside = exports.clipMeasurements = void 0;
+const clipMeasurements = (vm, cvm) => {
+ let { width, height, x: x0, y: y0, } = vm;
+ let x1 = x0 + width;
+ let y1 = y0 + height;
+ const { width: cwidth, height: cheight, x: cx0, y: cy0, } = cvm;
+ const cx1 = cx0 + cwidth;
+ const cy1 = cy0 + cheight;
+ if (x0 >= cx1 || x1 <= cx0 || y0 >= cy1 || y1 <= cy0) {
+ return {
+ x: -1,
+ y: -1,
+ width: 0,
+ height: 0,
+ };
+ }
+ if (x0 < cx0) {
+ width -= cx0 - x0;
+ x0 = cx0;
+ }
+ if (x1 > cx1) {
+ width -= x1 - cx1;
+ x1 = cx1;
+ }
+ if (y0 < cy0) {
+ height -= cy0 - y0;
+ y0 = cy0;
+ }
+ if (y1 > cy1) {
+ height -= y1 - cy1;
+ y1 = cy1;
+ }
+ return {
+ width,
+ height,
+ x: x0,
+ y: y0,
+ };
+};
+exports.clipMeasurements = clipMeasurements;
+const isPointInside = ({ x, y }, { width, height, x: x0, y: y0, }) => (x >= x0 && y >= y0 && x < x0 + width && y < y0 + height);
+exports.isPointInside = isPointInside;
+const getRelativePosition = ({ x, y }, { width, height, x: x0, y: y0, }) => {
+ const rx = x - x0;
+ const ry = y - y0;
+ return {
+ relativePosition: { x: rx, y: ry },
+ relativePositionRatio: { x: rx / width, y: ry / height },
+ };
+};
+exports.getRelativePosition = getRelativePosition;
+const extractPosition = ({ x, y }) => ({ x, y });
+exports.extractPosition = extractPosition;
+const extractDimensions = ({ width, height }) => ({ width, height });
+exports.extractDimensions = extractDimensions;
+/*
+ * Previously we were using the uuid library to generate unique identifiers for Drax
+ * components. Since we do not need them to be cryptographically secure and likely
+ * won't need very many of them, let's just use this simple function.
+ */
+const generateRandomId = () => (`${Math.random().toString(36).substr(2)}${Math.random().toString(36).substr(2)}`);
+exports.generateRandomId = generateRandomId;
diff --git a/build/params.d.ts b/build/params.d.ts
new file mode 100644
index 0000000..3ebd070
--- /dev/null
+++ b/build/params.d.ts
@@ -0,0 +1,18 @@
+/** Default snapback delay in milliseconds */
+export declare const defaultSnapbackDelay = 100;
+/** Default snapback duration in milliseconds */
+export declare const defaultSnapbackDuration = 250;
+/** Default pre-drag long press delay in milliseconds */
+export declare const defaultLongPressDelay = 0;
+/** Default pre-drag long press delay in milliseconds for DraxList items */
+export declare const defaultListItemLongPressDelay = 250;
+/** Default scroll event throttle (number of events per second) for DraxScrollView */
+export declare const defaultScrollEventThrottle = 8;
+/** Default interval length in milliseconds for auto-scrolling jumps */
+export declare const defaultAutoScrollIntervalLength = 250;
+/** Default auto-scroll jump distance, as a fraction relative to content width/length */
+export declare const defaultAutoScrollJumpRatio = 0.2;
+/** Default drag-over maximum position threshold for auto-scroll back, as a fraction relative to content width/length */
+export declare const defaultAutoScrollBackThreshold = 0.1;
+/** Default drag-over minimum position threshold for auto-scroll forward, as a fraction relative to content width/length */
+export declare const defaultAutoScrollForwardThreshold = 0.9;
diff --git a/build/params.js b/build/params.js
new file mode 100644
index 0000000..862f2f6
--- /dev/null
+++ b/build/params.js
@@ -0,0 +1,21 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.defaultAutoScrollForwardThreshold = exports.defaultAutoScrollBackThreshold = exports.defaultAutoScrollJumpRatio = exports.defaultAutoScrollIntervalLength = exports.defaultScrollEventThrottle = exports.defaultListItemLongPressDelay = exports.defaultLongPressDelay = exports.defaultSnapbackDuration = exports.defaultSnapbackDelay = void 0;
+/** Default snapback delay in milliseconds */
+exports.defaultSnapbackDelay = 100;
+/** Default snapback duration in milliseconds */
+exports.defaultSnapbackDuration = 250;
+/** Default pre-drag long press delay in milliseconds */
+exports.defaultLongPressDelay = 0;
+/** Default pre-drag long press delay in milliseconds for DraxList items */
+exports.defaultListItemLongPressDelay = 250;
+/** Default scroll event throttle (number of events per second) for DraxScrollView */
+exports.defaultScrollEventThrottle = 8;
+/** Default interval length in milliseconds for auto-scrolling jumps */
+exports.defaultAutoScrollIntervalLength = 250;
+/** Default auto-scroll jump distance, as a fraction relative to content width/length */
+exports.defaultAutoScrollJumpRatio = 0.2;
+/** Default drag-over maximum position threshold for auto-scroll back, as a fraction relative to content width/length */
+exports.defaultAutoScrollBackThreshold = 0.1;
+/** Default drag-over minimum position threshold for auto-scroll forward, as a fraction relative to content width/length */
+exports.defaultAutoScrollForwardThreshold = 0.9;
diff --git a/build/transform.d.ts b/build/transform.d.ts
new file mode 100644
index 0000000..b7206cd
--- /dev/null
+++ b/build/transform.d.ts
@@ -0,0 +1,4 @@
+import { Animated, StyleProp, ViewStyle } from 'react-native';
+import { AnimatedViewStyleWithoutLayout } from './types';
+export declare const flattenStylesWithoutLayout: (styles: StyleProp>[]) => AnimatedViewStyleWithoutLayout;
+export declare const mergeStyleTransform: (style: AnimatedViewStyleWithoutLayout, transform: Animated.WithAnimatedValue) => AnimatedViewStyleWithoutLayout;
diff --git a/build/transform.js b/build/transform.js
new file mode 100644
index 0000000..43aca3a
--- /dev/null
+++ b/build/transform.js
@@ -0,0 +1,17 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.mergeStyleTransform = exports.flattenStylesWithoutLayout = void 0;
+const react_native_1 = require("react-native");
+const flattenStylesWithoutLayout = (styles) => {
+ const { margin, marginHorizontal, marginVertical, marginLeft, marginRight, marginTop, marginBottom, marginStart, marginEnd, left, right, top, bottom, flex, flexBasis, flexDirection, flexGrow, flexShrink, ...flattened } = react_native_1.StyleSheet.flatten(styles);
+ return flattened;
+};
+exports.flattenStylesWithoutLayout = flattenStylesWithoutLayout;
+const mergeStyleTransform = (style, transform) => ({
+ ...style,
+ transform: [
+ ...(transform ?? []),
+ ...(style.transform ?? []),
+ ],
+});
+exports.mergeStyleTransform = mergeStyleTransform;
diff --git a/build/types.d.ts b/build/types.d.ts
new file mode 100644
index 0000000..734033c
--- /dev/null
+++ b/build/types.d.ts
@@ -0,0 +1,658 @@
+import { RefObject, ReactNode } from 'react';
+import { ViewProps, Animated, FlatListProps, ViewStyle, StyleProp, ScrollViewProps, ListRenderItemInfo, View } from 'react-native';
+import { LongPressGestureHandlerStateChangeEvent, LongPressGestureHandlerGestureEvent } from 'react-native-gesture-handler';
+import { PayloadActionCreator, ActionType } from 'typesafe-actions';
+/** Gesture state change event expected by Drax handler */
+export declare type DraxGestureStateChangeEvent = LongPressGestureHandlerStateChangeEvent['nativeEvent'];
+/** Gesture event expected by Drax handler */
+export declare type DraxGestureEvent = LongPressGestureHandlerGestureEvent['nativeEvent'];
+/** An xy-coordinate position value */
+export interface Position {
+ /** Position on horizontal x-axis, positive is right */
+ x: number;
+ /** Position on vertical y-axis, positive is down */
+ y: number;
+}
+/** Predicate for checking if something is a Position */
+export declare const isPosition: (something: any) => something is Position;
+/** Dimensions of a view */
+export interface ViewDimensions {
+ /** Width of view */
+ width: number;
+ /** Height of view */
+ height: number;
+}
+/** Measurements of a Drax view for bounds checking purposes, relative to Drax parent view or DraxProvider (absolute) */
+export interface DraxViewMeasurements extends Position, ViewDimensions {
+}
+/** Data about a view involved in a Drax event */
+export interface DraxEventViewData {
+ /** The view's id */
+ id: string;
+ /** The view's parent id, if any */
+ parentId?: string;
+ /** The view's payload for this event */
+ payload: any;
+}
+/** Data about a dragged view involved in a Drax event */
+export interface DraxEventDraggedViewData extends DraxEventViewData {
+ /** The ratio of the drag translation to the dimensions of the view */
+ dragTranslationRatio: Position;
+ /** The relative offset of the drag point from the view */
+ dragOffset: Position;
+ /** The relative offset of where the view was grabbed */
+ grabOffset: Position;
+ /** The relative offset/dimensions ratio of where the view was grabbed */
+ grabOffsetRatio: Position;
+ /** The position in absolute coordinates of the dragged hover view (dragAbsolutePosition - grabOffset) */
+ hoverPosition: Animated.ValueXY;
+}
+/** Data about a receiver view involved in a Drax event */
+export interface DraxEventReceiverViewData extends DraxEventViewData {
+ /** The relative offset of the drag point in the receiving view */
+ receiveOffset: Position;
+ /** The relative offset/dimensions ratio of the drag point in the receiving view */
+ receiveOffsetRatio: Position;
+}
+/** Data about a Drax drag event */
+export interface DraxDragEventData {
+ /** Position of the drag event in absolute coordinates */
+ dragAbsolutePosition: Position;
+ /** The absolute drag distance from where the drag started */
+ dragTranslation: Position;
+ /** Data about the dragged view */
+ dragged: DraxEventDraggedViewData;
+}
+/** Supplemental type for adding a cancelled flag */
+export interface WithCancelledFlag {
+ /** True if the event was cancelled */
+ cancelled: boolean;
+}
+/** Predicate for checking if something has a cancelled flag */
+export declare const isWithCancelledFlag: (something: any) => something is WithCancelledFlag;
+/** Data about a Drax drag end event */
+export interface DraxDragEndEventData extends DraxDragEventData, WithCancelledFlag {
+}
+/** Data about a Drax drag event that involves a receiver */
+export interface DraxDragWithReceiverEventData extends DraxDragEventData {
+ /** The receiver for the drag event */
+ receiver: DraxEventReceiverViewData;
+}
+/** Data about a Drax drag/receive end event */
+export interface DraxDragWithReceiverEndEventData extends DraxDragWithReceiverEventData, WithCancelledFlag {
+}
+/** Data about a Drax snapback, used for custom animations */
+export interface DraxSnapbackData {
+ hoverPosition: Animated.ValueXY;
+ toValue: Position;
+ delay: number;
+ duration: number;
+}
+/** Data about a Drax monitor event */
+export interface DraxMonitorEventData extends DraxDragEventData {
+ /** The receiver for the monitor event, if any */
+ receiver?: DraxEventReceiverViewData;
+ /** Event position relative to the monitor */
+ monitorOffset: Position;
+ /** Event position/dimensions ratio relative to the monitor */
+ monitorOffsetRatio: Position;
+}
+/** Data about a Drax monitor drag end event */
+export interface DraxMonitorEndEventData extends DraxMonitorEventData, WithCancelledFlag {
+}
+/** Data about a Drax monitor drag-drop event */
+export interface DraxMonitorDragDropEventData extends Required {
+}
+/** Preset values for specifying snapback targets without a Position */
+export declare enum DraxSnapbackTargetPreset {
+ Default = 0,
+ None = 1
+}
+/** Target for snapback hover view release animation: none, default, or specified Position */
+export declare type DraxSnapbackTarget = DraxSnapbackTargetPreset | Position;
+/**
+ * Response type for Drax protocol callbacks involving end of a drag,
+ * allowing override of default release snapback behavior.
+ */
+export declare type DraxProtocolDragEndResponse = void | DraxSnapbackTarget;
+/** Props provided to an internal render function for a hovering copy of a Drax view */
+export interface DraxInternalRenderHoverViewProps {
+ /** Key of the hover view React node */
+ key: string;
+ /** Hover position of the view */
+ hoverPosition: Animated.ValueXY;
+ /** State for the view */
+ viewState: DraxViewState;
+ /** Drax tracking status */
+ trackingStatus: DraxTrackingStatus;
+ /** Dimensions for the view */
+ dimensions: ViewDimensions;
+}
+/** Props provided to a render function for a Drax view */
+export interface DraxRenderContentProps {
+ /** State for the view, if available */
+ viewState?: DraxViewState;
+ /** Drax tracking status */
+ trackingStatus: DraxTrackingStatus;
+ /** Is this a hovering copy of the view? */
+ hover: boolean;
+ /** React children of the DraxView */
+ children: ReactNode;
+ /** Dimensions for the view, if available */
+ dimensions?: ViewDimensions;
+}
+/** Props provided to a render function for a hovering copy of a Drax view, compatible with DraxRenderContentProps */
+export interface DraxRenderHoverContentProps extends Required {
+}
+/** Callback protocol for communicating Drax events to views */
+export interface DraxProtocol {
+ /** Called in the dragged view when a drag action begins */
+ onDragStart?: (data: DraxDragEventData) => void;
+ /** Called in the dragged view repeatedly while dragged, not over any receiver */
+ onDrag?: (data: DraxDragEventData) => void;
+ /** Called in the dragged view when initially dragged over a new receiver */
+ onDragEnter?: (data: DraxDragWithReceiverEventData) => void;
+ /** Called in the dragged view repeatedly while dragged over a receiver */
+ onDragOver?: (data: DraxDragWithReceiverEventData) => void;
+ /** Called in the dragged view when dragged off of a receiver */
+ onDragExit?: (data: DraxDragWithReceiverEventData) => void;
+ /** Called in the dragged view when drag ends not over any receiver or is cancelled */
+ onDragEnd?: (data: DraxDragEndEventData) => DraxProtocolDragEndResponse;
+ /** Called in the dragged view when drag ends over a receiver */
+ onDragDrop?: (data: DraxDragWithReceiverEventData) => DraxProtocolDragEndResponse;
+ /** Called in the dragged view when drag release snapback ends */
+ onSnapbackEnd?: () => void;
+ /** Called in the receiver view each time an item is initially dragged over it */
+ onReceiveDragEnter?: (data: DraxDragWithReceiverEventData) => void;
+ /** Called in the receiver view repeatedly while an item is dragged over it */
+ onReceiveDragOver?: (data: DraxDragWithReceiverEventData) => void;
+ /** Called in the receiver view when item is dragged off of it or drag is cancelled */
+ onReceiveDragExit?: (data: DraxDragWithReceiverEndEventData) => void;
+ /** Called in the receiver view when drag ends over it */
+ onReceiveDragDrop?: (data: DraxDragWithReceiverEventData) => DraxProtocolDragEndResponse;
+ /** Called in the monitor view when a drag action begins over it */
+ onMonitorDragStart?: (data: DraxMonitorEventData) => void;
+ /** Called in the monitor view each time an item is initially dragged over it */
+ onMonitorDragEnter?: (data: DraxMonitorEventData) => void;
+ /** Called in the monitor view repeatedly while an item is dragged over it */
+ onMonitorDragOver?: (data: DraxMonitorEventData) => void;
+ /** Called in the monitor view when item is dragged off of it */
+ onMonitorDragExit?: (data: DraxMonitorEventData) => void;
+ /** Called in the monitor view when drag ends over it while not over any receiver or drag is cancelled */
+ onMonitorDragEnd?: (data: DraxMonitorEndEventData) => DraxProtocolDragEndResponse;
+ /** Called in the monitor view when drag ends over it while over a receiver */
+ onMonitorDragDrop?: (data: DraxMonitorDragDropEventData) => DraxProtocolDragEndResponse;
+ /** Whether or not to animate hover view snapback after drag release, defaults to true */
+ animateSnapback?: boolean;
+ /** Delay in ms before hover view snapback begins after drag is released */
+ snapbackDelay?: number;
+ /** Duration in ms for hover view snapback to complete */
+ snapbackDuration?: number;
+ /** Function returning custom hover view snapback animation */
+ snapbackAnimator?: (data: DraxSnapbackData) => Animated.CompositeAnimation;
+ /** Payload that will be delivered to receiver views when this view is dragged; overrides `payload` */
+ dragPayload?: any;
+ /** Payload that will be delievered to dragged views when this view receives them; overrides `payload` */
+ receiverPayload?: any;
+ /** Whether the view can be dragged */
+ draggable: boolean;
+ /** Whether the view can receive drags */
+ receptive: boolean;
+ /** Whether the view can monitor drags */
+ monitoring: boolean;
+ /** If true, lock drag's x-position */
+ lockDragXPosition?: boolean;
+ /** If true, lock drag's y position */
+ lockDragYPosition?: boolean;
+ /** Function used internally for rendering hovering copy of view when dragged/released */
+ internalRenderHoverView?: (props: DraxInternalRenderHoverViewProps) => ReactNode;
+}
+/** Props for components implementing the protocol */
+export interface DraxProtocolProps extends Partial> {
+ /** Convenience prop to provide one value for both `dragPayload` and `receiverPayload` */
+ payload?: any;
+}
+/** The states a dragged view can be in */
+export declare enum DraxViewDragStatus {
+ /** View is not being dragged */
+ Inactive = 0,
+ /** View is being actively dragged; an active drag touch began in this view */
+ Dragging = 1,
+ /** View has been released but has not yet snapped back to inactive */
+ Released = 2
+}
+/** The states a receiver view can be in */
+export declare enum DraxViewReceiveStatus {
+ /** View is not receiving a drag */
+ Inactive = 0,
+ /** View is receiving a drag; an active drag touch point is currently over this view */
+ Receiving = 1
+}
+/** Information about a view, used internally by the Drax provider */
+export interface DraxViewData {
+ /** The view's Drax parent view id, if nested */
+ parentId?: string;
+ /** The view's scroll position ref, if it is a scrollable parent view */
+ scrollPositionRef?: RefObject;
+ /** The view's protocol callbacks and data */
+ protocol: DraxProtocol;
+ /** The view's measurements for bounds checking */
+ measurements?: DraxViewMeasurements;
+}
+/** Information about a view, plus its clipped absolute measurements */
+export interface DraxAbsoluteViewData extends Omit, Required> {
+ /** Absolute measurements for view */
+ absoluteMeasurements: DraxViewMeasurements;
+}
+/** Wrapper of id and absolute data for a view */
+export interface DraxAbsoluteViewEntry {
+ /** The view's unique identifier */
+ id: string;
+ data: DraxAbsoluteViewData;
+}
+/** Wrapper of id and absolute data for a view found when checking a position */
+export interface DraxFoundAbsoluteViewEntry extends DraxAbsoluteViewEntry {
+ /** Position, relative to the view, of the touch for which it was found */
+ relativePosition: Position;
+ /** Position/dimensions ratio, relative to the view, of the touch for which it was found */
+ relativePositionRatio: Position;
+}
+/** Tracking information about the current receiver, used internally by the Drax provider */
+export interface DraxTrackingReceiver {
+ /** View id of the current receiver */
+ receiverId: string;
+ /** The relative offset of the drag point in the receiving view */
+ receiveOffset: Position;
+ /** The relative offset/dimensions ratio of the drag point in the receiving view */
+ receiveOffsetRatio: Position;
+}
+/** Tracking information about the current drag, used internally by the Drax provider */
+export interface DraxTrackingDrag {
+ /** View id of the dragged view */
+ draggedId: string;
+ /** Start position of the drag in absolute coordinates */
+ absoluteStartPosition: Position;
+ /** Start position of the drag relative to dragged view's immediate parent */
+ parentStartPosition: Position;
+ /** The position in absolute coordinates of the drag point */
+ dragAbsolutePosition: Position;
+ /** The absolute drag distance from where the drag started (dragAbsolutePosition - absoluteStartPosition) */
+ dragTranslation: Position;
+ /** The ratio of the drag translation to the dimensions of the view */
+ dragTranslationRatio: Position;
+ /** The relative offset of the drag point from the view */
+ dragOffset: Position;
+ /** The relative offset within the dragged view of where it was grabbed */
+ grabOffset: Position;
+ /** The relative offset/dimensions ratio within the dragged view of where it was grabbed */
+ grabOffsetRatio: Position;
+ /** The position in absolute coordinates of the dragged hover view (dragAbsolutePosition - grabOffset) */
+ hoverPosition: Animated.ValueXY;
+ /** Tracking information about the current drag receiver, if any */
+ receiver?: DraxTrackingReceiver;
+ /** View ids of monitors that the drag is currently over */
+ monitorIds: string[];
+}
+/** Tracking information about a view that was released and is snapping back */
+export interface DraxTrackingRelease {
+ /** View id of the released view */
+ viewId: string;
+ /** The position in absolute coordinates of the released hover view */
+ hoverPosition: Animated.ValueXY;
+}
+/** Tracking status for reference in views */
+export interface DraxTrackingStatus {
+ /** Is any view being dragged? */
+ dragging: boolean;
+ /** Is any view receiving a drag? */
+ receiving: boolean;
+}
+/** Render-related state for a registered view */
+export interface DraxViewState {
+ /** Current drag status of the view: Dragged, Released, or Inactive */
+ dragStatus: DraxViewDragStatus;
+ /** If being dragged, the position in absolute coordinates of the drag point */
+ dragAbsolutePosition?: Position;
+ /** If being dragged, the absolute drag distance from where the drag started (dragAbsolutePosition - absoluteStartPosition) */
+ dragTranslation?: Position;
+ /** If being dragged, the ratio of the drag translation to the dimensions of the view */
+ dragTranslationRatio?: Position;
+ /** If being dragged, the relative offset of the drag point from the view */
+ dragOffset?: Position;
+ /** If being dragged, the relative offset of where the view was grabbed */
+ grabOffset?: Position;
+ /** If being dragged, the relative offset/dimensions ratio of where the view was grabbed */
+ grabOffsetRatio?: Position;
+ /** The position in absolute coordinates of the dragged hover view (dragAbsolutePosition - grabOffset) */
+ hoverPosition?: Animated.ValueXY;
+ /** Data about the receiver this view is being dragged over, if any */
+ draggingOverReceiver?: DraxEventViewData;
+ /** Current receive status of the view: Receiving or Inactive */
+ receiveStatus: DraxViewReceiveStatus;
+ /** If receiving a drag, the relative offset of the drag point in the view */
+ receiveOffset?: Position;
+ /** If receiving a drag, the relative offset/dimensions ratio of the drag point in the view */
+ receiveOffsetRatio?: Position;
+ /** Data about the dragged item this view is receiving, if any */
+ receivingDrag?: DraxEventViewData;
+}
+/** Drax provider render state; maintains render-related data */
+export interface DraxState {
+ /** Render-related state for all registered views, keyed by their unique identifiers */
+ viewStateById: {
+ /** Render-related state for a registered view, keyed by its unique identifier */
+ [id: string]: DraxViewState;
+ };
+ /** Tracking status indicating whether anything is being dragged/received */
+ trackingStatus: DraxTrackingStatus;
+}
+/** Payload to start tracking a drag */
+export interface StartDragPayload {
+ /** Absolute position of where the drag started */
+ dragAbsolutePosition: Position;
+ /** Position relative to the dragged view's immediate parent where the drag started */
+ dragParentPosition: Position;
+ /** The dragged view's unique identifier */
+ draggedId: string;
+ /** The relative offset within the view of where it was grabbed */
+ grabOffset: Position;
+ /** The relative offset/dimensions ratio within the view of where it was grabbed */
+ grabOffsetRatio: Position;
+}
+/** Payload for registering a Drax view */
+export interface RegisterViewPayload {
+ /** The view's unique identifier */
+ id: string;
+ /** The view's Drax parent view id, if nested */
+ parentId?: string;
+ /** The view's scroll position ref, if it is a scrollable parent view */
+ scrollPositionRef?: RefObject;
+}
+/** Payload for unregistering a Drax view */
+export interface UnregisterViewPayload {
+ /** The view's unique identifier */
+ id: string;
+}
+/** Payload for updating the protocol values of a registered view */
+export interface UpdateViewProtocolPayload {
+ /** The view's unique identifier */
+ id: string;
+ /** The current protocol values for the view */
+ protocol: DraxProtocol;
+}
+/** Payload for reporting the latest measurements of a view after layout */
+export interface UpdateViewMeasurementsPayload {
+ /** The view's unique identifier */
+ id: string;
+ /** The view's measurements */
+ measurements: DraxViewMeasurements | undefined;
+}
+/** Payload used by Drax provider internally for creating a view's state */
+export interface CreateViewStatePayload {
+ /** The view's unique identifier */
+ id: string;
+}
+/** Payload used by Drax provider internally for updating a view's state */
+export interface UpdateViewStatePayload {
+ /** The view's unique identifier */
+ id: string;
+ /** The view state update */
+ viewStateUpdate: Partial;
+}
+/** Payload used by Drax provider internally for deleting a view's state */
+export interface DeleteViewStatePayload {
+ /** The view's unique identifier */
+ id: string;
+}
+/** Payload used by Drax provider internally for updating tracking status */
+export interface UpdateTrackingStatusPayload extends Partial {
+}
+/** Collection of Drax state action creators */
+export interface DraxStateActionCreators {
+ createViewState: PayloadActionCreator<'createViewState', CreateViewStatePayload>;
+ updateViewState: PayloadActionCreator<'updateViewState', UpdateViewStatePayload>;
+ deleteViewState: PayloadActionCreator<'deleteViewState', DeleteViewStatePayload>;
+ updateTrackingStatus: PayloadActionCreator<'updateTrackingStatus', UpdateTrackingStatusPayload>;
+}
+/** Dispatchable Drax state action */
+export declare type DraxStateAction = ActionType;
+/** Dispatcher of Drax state actions */
+export declare type DraxStateDispatch = (action: DraxStateAction) => void;
+/** Drax provider internal registry; maintains view data and tracks drags, updating state */
+export interface DraxRegistry {
+ /** A list of the unique identifiers of the registered views, in order of registration */
+ viewIds: string[];
+ /** Data about all registered views, keyed by their unique identifiers */
+ viewDataById: {
+ /** Data about a registered view, keyed by its unique identifier */
+ [id: string]: DraxViewData;
+ };
+ /** Information about the current drag, if any */
+ drag?: DraxTrackingDrag;
+ /** A list of the unique identifiers of tracked drag releases, in order of release */
+ releaseIds: string[];
+ /** Released drags that are snapping back, keyed by unique release identifier */
+ releaseById: {
+ [releaseId: string]: DraxTrackingRelease;
+ };
+ /** Drax state dispatch function */
+ stateDispatch: DraxStateDispatch;
+}
+/** Context value used internally by Drax provider */
+export interface DraxContextValue {
+ /** Get a Drax view state by view id, if it exists */
+ getViewState: (id: string) => DraxViewState | undefined;
+ /** Get current Drax tracking status */
+ getTrackingStatus: () => DraxTrackingStatus;
+ /** Register a Drax view */
+ registerView: (payload: RegisterViewPayload) => void;
+ /** Unregister a Drax view */
+ unregisterView: (payload: UnregisterViewPayload) => void;
+ /** Update protocol for a registered Drax view */
+ updateViewProtocol: (payload: UpdateViewProtocolPayload) => void;
+ /** Update view measurements for a registered Drax view */
+ updateViewMeasurements: (payload: UpdateViewMeasurementsPayload) => void;
+ /** Handle gesture state change for a registered Drax view */
+ handleGestureStateChange: (id: string, event: DraxGestureStateChangeEvent) => void;
+ /** Handle gesture event for a registered Drax view */
+ handleGestureEvent: (id: string, event: DraxGestureEvent) => void;
+ /** Root node handle ref for the Drax provider, for measuring non-parented views in relation to */
+ rootNodeHandleRef: RefObject;
+ /** Drax parent view for all views under this context, when nesting */
+ parent?: DraxParentView;
+}
+/** Optional props that can be passed to a DraxProvider to modify its behavior */
+export interface DraxProviderProps {
+ style?: StyleProp;
+ debug?: boolean;
+ children?: ReactNode;
+}
+/** Props that are passed to a DraxSubprovider, used internally for nesting views */
+export interface DraxSubproviderProps {
+ /** Drax parent view for all views under this subprovider, when nesting */
+ parent: DraxParentView;
+}
+/** Methods provided by a DraxView when registered externally */
+export interface DraxViewRegistration {
+ id: string;
+ measure: (measurementHandler?: DraxViewMeasurementHandler) => void;
+}
+/** Information about the parent of a nested DraxView, primarily used for scrollable parent views */
+export interface DraxParentView {
+ /** Drax view id of the parent */
+ id: string;
+ /** Ref to node handle of the parent, for measuring relative to */
+ nodeHandleRef: RefObject;
+}
+/** Function that receives a Drax view measurement */
+export interface DraxViewMeasurementHandler {
+ (measurements: DraxViewMeasurements | undefined): void;
+}
+/** Layout-related style keys that are omitted from hover view styles */
+export declare type LayoutStyleKey = ('margin' | 'marginHorizontal' | 'marginVertical' | 'marginLeft' | 'marginRight' | 'marginTop' | 'marginBottom' | 'marginStart' | 'marginEnd' | 'left' | 'right' | 'top' | 'bottom' | 'flex' | 'flexBasis' | 'flexDirection' | 'flexGrow' | 'flexShrink');
+/** Style for a Animated.View used for a hover view */
+export declare type AnimatedViewStyleWithoutLayout = Omit, LayoutStyleKey>;
+/** Style prop for a Animated.View used for a hover view */
+export declare type AnimatedViewStylePropWithoutLayout = StyleProp;
+/** Style-related props for a Drax view */
+export interface DraxViewStyleProps {
+ /** Custom style prop to allow animated values */
+ style?: StyleProp>;
+ /** Additional view style applied while this view is not being dragged or released */
+ dragInactiveStyle?: StyleProp>;
+ /** Additional view style applied while this view is being dragged */
+ draggingStyle?: StyleProp>;
+ /** Additional view style applied while this view is being dragged over a receiver */
+ draggingWithReceiverStyle?: StyleProp>;
+ /** Additional view style applied while this view is being dragged NOT over a receiver */
+ draggingWithoutReceiverStyle?: StyleProp>;
+ /** Additional view style applied while this view has just been released from a drag */
+ dragReleasedStyle?: StyleProp>;
+ /** Additional view style applied to the hovering copy of this view during drag/release */
+ hoverStyle?: AnimatedViewStylePropWithoutLayout;
+ /** Additional view style applied to the hovering copy of this view while dragging */
+ hoverDraggingStyle?: AnimatedViewStylePropWithoutLayout;
+ /** Additional view style applied to the hovering copy of this view while dragging over a receiver */
+ hoverDraggingWithReceiverStyle?: AnimatedViewStylePropWithoutLayout;
+ /** Additional view style applied to the hovering copy of this view while dragging NOT over a receiver */
+ hoverDraggingWithoutReceiverStyle?: AnimatedViewStylePropWithoutLayout;
+ /** Additional view style applied to the hovering copy of this view when just released */
+ hoverDragReleasedStyle?: AnimatedViewStylePropWithoutLayout;
+ /** Additional view style applied while this view is not receiving a drag */
+ receiverInactiveStyle?: StyleProp>;
+ /** Additional view style applied while this view is receiving a drag */
+ receivingStyle?: StyleProp>;
+ /** Additional view style applied to this view while any other view is being dragged */
+ otherDraggingStyle?: StyleProp>;
+ /** Additional view style applied to this view while any other view is being dragged over a receiver */
+ otherDraggingWithReceiverStyle?: StyleProp>;
+ /** Additional view style applied to this view while any other view is being dragged NOT over a receiver */
+ otherDraggingWithoutReceiverStyle?: StyleProp>;
+}
+/** Custom render function for content of a DraxView */
+export interface DraxViewRenderContent {
+ (props: DraxRenderContentProps): ReactNode;
+}
+/** Custom render function for content of hovering copy of a DraxView */
+export interface DraxViewRenderHoverContent {
+ (props: DraxRenderHoverContentProps): ReactNode;
+}
+/** Props for a DraxView; combines protocol props and standard view props */
+export interface DraxViewProps extends Omit, DraxProtocolProps, DraxViewStyleProps {
+ /** Custom render function for content of this view */
+ renderContent?: DraxViewRenderContent;
+ /** Custom render function for content of hovering copy of this view, defaults to renderContent */
+ renderHoverContent?: DraxViewRenderHoverContent;
+ /** If true, do not render hover view copies for this view when dragging */
+ noHover?: boolean;
+ /** For external registration of this view, to access internal methods, similar to a ref */
+ registration?: (registration: DraxViewRegistration | undefined) => void;
+ /** For receiving view measurements externally */
+ onMeasure?: DraxViewMeasurementHandler;
+ /** For receiving view measurements externally */
+ viewRef?: React.MutableRefObject | ((viewRef: View | null) => void);
+ /** Unique Drax view id, auto-generated if omitted */
+ id?: string;
+ /** Drax parent view, if nesting */
+ parent?: DraxParentView;
+ /** If true, treat this view as a Drax parent view for nested children */
+ isParent?: boolean;
+ /** The view's scroll position ref, if it is a scrollable parent view */
+ scrollPositionRef?: RefObject;
+ /** Time in milliseconds view needs to be pressed before drag starts */
+ longPressDelay?: number;
+}
+/** Auto-scroll direction used internally by DraxScrollView and DraxList */
+export declare enum AutoScrollDirection {
+ /** Auto-scrolling back toward the beginning of list */
+ Back = -1,
+ /** Not auto-scrolling */
+ None = 0,
+ /** Auto-scrolling forward toward the end of the list */
+ Forward = 1
+}
+/** Auto-scroll state used internally by DraxScrollView */
+export interface AutoScrollState {
+ x: AutoScrollDirection;
+ y: AutoScrollDirection;
+}
+/** Props for auto-scroll options, used by DraxScrollView and DraxList */
+export interface DraxAutoScrollProps {
+ autoScrollIntervalLength?: number;
+ autoScrollJumpRatio?: number;
+ autoScrollBackThreshold?: number;
+ autoScrollForwardThreshold?: number;
+}
+/** Props for a DraxScrollView; extends standard ScrollView props */
+export interface DraxScrollViewProps extends ScrollViewProps, DraxAutoScrollProps {
+ /** Unique drax view id, auto-generated if omitted */
+ id?: string;
+}
+/** DraxList item being dragged */
+export interface DraxListDraggedItemData {
+ index: number;
+ item?: TItem;
+}
+/** Event data for when a list item reorder drag action begins */
+export interface DraxListOnItemDragStartEventData extends DraxDragEventData, DraxListDraggedItemData {
+}
+/** Event data for when a list item position (index) changes during a reorder drag */
+export interface DraxListOnItemDragPositionChangeEventData extends DraxMonitorEventData, DraxListDraggedItemData {
+ toIndex: number | undefined;
+ previousIndex: number | undefined;
+}
+/** Event data for when a list item reorder drag action ends */
+export interface DraxListOnItemDragEndEventData extends DraxMonitorEventData, WithCancelledFlag, DraxListDraggedItemData {
+ toIndex?: number;
+ toItem?: TItem;
+}
+/** Event data for when an item is released in a new position within a DraxList, reordering the list */
+export interface DraxListOnItemReorderEventData {
+ fromItem: TItem;
+ fromIndex: number;
+ toItem: TItem;
+ toIndex: number;
+}
+/** Render function for content of a DraxList item's DraxView */
+export interface DraxListRenderItemContent {
+ (info: ListRenderItemInfo, props: DraxRenderContentProps): ReactNode;
+}
+/** Render function for content of a DraxList item's hovering copy */
+export interface DraxListRenderItemHoverContent {
+ (info: ListRenderItemInfo, props: DraxRenderHoverContentProps): ReactNode;
+}
+/** Callback handler for when a list item is moved within a DraxList, reordering the list */
+export interface DraxListOnItemReorder {
+ (eventData: DraxListOnItemReorderEventData): void;
+}
+/** Props for a DraxList; extends standard FlatList props */
+export interface DraxListProps extends Omit, 'renderItem'>, DraxAutoScrollProps {
+ /** Unique drax view id, auto-generated if omitted */
+ id?: string;
+ /** Style prop for the underlying FlatList */
+ flatListStyle?: StyleProp;
+ /** Style props to apply to all DraxView items in the list */
+ itemStyles?: DraxViewStyleProps;
+ /** Render function for content of an item's DraxView */
+ renderItemContent: DraxListRenderItemContent;
+ /** Render function for content of an item's hovering copy, defaults to renderItemContent */
+ renderItemHoverContent?: DraxListRenderItemHoverContent;
+ /** Callback handler for when a list item reorder drag action begins */
+ onItemDragStart?: (eventData: DraxListOnItemDragStartEventData) => void;
+ /** Callback handler for when a list item position (index) changes during a reorder drag */
+ onItemDragPositionChange?: (eventData: DraxListOnItemDragPositionChangeEventData) => void;
+ /** Callback handler for when a list item reorder drag action ends */
+ onItemDragEnd?: (eventData: DraxListOnItemDragEndEventData) => void;
+ /** Callback handler for when a list item is moved within the list, reordering the list */
+ onItemReorder?: DraxListOnItemReorder;
+ /** Can the list be reordered by dragging items? Defaults to true if onItemReorder is set. */
+ reorderable?: boolean;
+ /** Can the items be dragged? Defaults to true. */
+ itemsDraggable?: boolean;
+ /** If true, lock item drags to the list's main axis */
+ lockItemDragsToMainAxis?: boolean;
+ /** Time in milliseconds view needs to be pressed before drag starts */
+ longPressDelay?: number;
+ /** Function that receives an item and returns a list of DraxViewProps to apply to that item's DraxView */
+ viewPropsExtractor?: (item: TItem) => Partial;
+}
diff --git a/build/types.js b/build/types.js
new file mode 100644
index 0000000..2689479
--- /dev/null
+++ b/build/types.js
@@ -0,0 +1,43 @@
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.AutoScrollDirection = exports.DraxViewReceiveStatus = exports.DraxViewDragStatus = exports.DraxSnapbackTargetPreset = exports.isWithCancelledFlag = exports.isPosition = void 0;
+/** Predicate for checking if something is a Position */
+const isPosition = (something) => (typeof something === 'object' && something !== null && typeof something.x === 'number' && typeof something.y === 'number');
+exports.isPosition = isPosition;
+/** Predicate for checking if something has a cancelled flag */
+const isWithCancelledFlag = (something) => (typeof something === 'object' && something !== null && typeof something.cancelled === 'boolean');
+exports.isWithCancelledFlag = isWithCancelledFlag;
+/** Preset values for specifying snapback targets without a Position */
+var DraxSnapbackTargetPreset;
+(function (DraxSnapbackTargetPreset) {
+ DraxSnapbackTargetPreset[DraxSnapbackTargetPreset["Default"] = 0] = "Default";
+ DraxSnapbackTargetPreset[DraxSnapbackTargetPreset["None"] = 1] = "None";
+})(DraxSnapbackTargetPreset = exports.DraxSnapbackTargetPreset || (exports.DraxSnapbackTargetPreset = {}));
+/** The states a dragged view can be in */
+var DraxViewDragStatus;
+(function (DraxViewDragStatus) {
+ /** View is not being dragged */
+ DraxViewDragStatus[DraxViewDragStatus["Inactive"] = 0] = "Inactive";
+ /** View is being actively dragged; an active drag touch began in this view */
+ DraxViewDragStatus[DraxViewDragStatus["Dragging"] = 1] = "Dragging";
+ /** View has been released but has not yet snapped back to inactive */
+ DraxViewDragStatus[DraxViewDragStatus["Released"] = 2] = "Released";
+})(DraxViewDragStatus = exports.DraxViewDragStatus || (exports.DraxViewDragStatus = {}));
+/** The states a receiver view can be in */
+var DraxViewReceiveStatus;
+(function (DraxViewReceiveStatus) {
+ /** View is not receiving a drag */
+ DraxViewReceiveStatus[DraxViewReceiveStatus["Inactive"] = 0] = "Inactive";
+ /** View is receiving a drag; an active drag touch point is currently over this view */
+ DraxViewReceiveStatus[DraxViewReceiveStatus["Receiving"] = 1] = "Receiving";
+})(DraxViewReceiveStatus = exports.DraxViewReceiveStatus || (exports.DraxViewReceiveStatus = {}));
+/** Auto-scroll direction used internally by DraxScrollView and DraxList */
+var AutoScrollDirection;
+(function (AutoScrollDirection) {
+ /** Auto-scrolling back toward the beginning of list */
+ AutoScrollDirection[AutoScrollDirection["Back"] = -1] = "Back";
+ /** Not auto-scrolling */
+ AutoScrollDirection[AutoScrollDirection["None"] = 0] = "None";
+ /** Auto-scrolling forward toward the end of the list */
+ AutoScrollDirection[AutoScrollDirection["Forward"] = 1] = "Forward";
+})(AutoScrollDirection = exports.AutoScrollDirection || (exports.AutoScrollDirection = {}));
diff --git a/src/DraxView.tsx b/src/DraxView.tsx
index ef96586..776863b 100644
--- a/src/DraxView.tsx
+++ b/src/DraxView.tsx
@@ -89,6 +89,7 @@ export const DraxView = (
lockDragXPosition,
lockDragYPosition,
children,
+ viewRef: inputViewRef,
noHover = false,
isParent = false,
longPressDelay = defaultLongPressDelay,
@@ -595,6 +596,13 @@ export const DraxView = (
const setViewRefs = useCallback(
(ref: View | null) => {
+ if (inputViewRef){
+ if (typeof(inputViewRef) === 'function'){
+ inputViewRef(ref);
+ } else {
+ inputViewRef.current = ref;
+ }
+ }
viewRef.current = ref;
nodeHandleRef.current = ref && findNodeHandle(ref);
},
diff --git a/src/hooks/useDraxRegistry.ts b/src/hooks/useDraxRegistry.ts
index 60e476e..785c373 100644
--- a/src/hooks/useDraxRegistry.ts
+++ b/src/hooks/useDraxRegistry.ts
@@ -145,6 +145,18 @@ const getAbsoluteViewEntryFromRegistry = (
return data && { id, data };
};
+/**
+ * If multiple recievers match, we need to pick the one that is on top. This
+ * is first done by filtering out all that are parents (because parent views are below child ones)
+ * and then if there are any further possibilities, it chooses the smallest one.
+ */
+const getTopMostReceiver = (receivers: DraxFoundAbsoluteViewEntry[]) => {
+ const ids = receivers.map(receiver => receiver.data.parentId);
+ receivers = receivers.filter(receiver => !ids.includes(receiver.id));
+ receivers.sort((receiverA, receiverB) => receiverA.data.measurements.height*receiverA.data.measurements.width - receiverB.data.measurements.height*receiverB.data.measurements.width);
+ return receivers[0];
+};
+
/**
* Find all monitoring views and the latest receptive view that
* contain the touch coordinates, excluding the specified view.
@@ -155,7 +167,7 @@ const findMonitorsAndReceiverInRegistry = (
excludeViewId: string,
) => {
const monitors: DraxFoundAbsoluteViewEntry[] = [];
- let receiver: DraxFoundAbsoluteViewEntry | undefined;
+ let receivers: DraxFoundAbsoluteViewEntry[] = [];
// console.log(`find monitors and receiver for absolute position (${absolutePosition.x}, ${absolutePosition.y})`);
registry.viewIds.forEach((targetId) => {
@@ -212,14 +224,14 @@ const findMonitorsAndReceiverInRegistry = (
if (receptive) {
// It's the latest receiver found.
- receiver = foundView;
+ receivers.push(foundView);
// console.log('it\'s a receiver');
}
}
});
return {
monitors,
- receiver,
+ receiver: getTopMostReceiver(receivers)
};
};
diff --git a/src/types.ts b/src/types.ts
index 399f279..7b796fc 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -7,6 +7,7 @@ import {
StyleProp,
ScrollViewProps,
ListRenderItemInfo,
+ View,
} from 'react-native';
import {
LongPressGestureHandlerStateChangeEvent,
@@ -718,6 +719,9 @@ export interface DraxViewProps extends Omit, DraxProtocolPro
/** For receiving view measurements externally */
onMeasure?: DraxViewMeasurementHandler;
+ /** For receiving view measurements externally */
+ viewRef?: React.MutableRefObject | ((viewRef: View|null) => void);
+
/** Unique Drax view id, auto-generated if omitted */
id?: string;