UNPKG

react-easy-sort

Version:

A React component to sort items in lists or grids

824 lines (819 loc) 32.8 kB
import { __spreadArray, __rest, __assign } from 'tslib'; import React from 'react'; /** * This function check if a given point is inside of the items rect. * If it's not inside any rect, it will return the index of the closest rect */ var findItemIndexAtPosition = function findItemIndexAtPosition(_a, itemsRect, _b) { var x = _a.x, y = _a.y; var _c = _b === void 0 ? {} : _b, _d = _c.fallbackToClosest, fallbackToClosest = _d === void 0 ? false : _d; var smallestDistance = 10000; var smallestDistanceIndex = -1; for (var index = 0; index < itemsRect.length; index += 1) { var rect = itemsRect[index]; // if it's inside the rect, we return the current index directly if (x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom) { return index; } if (fallbackToClosest) { // otherwise we compute the distance and update the smallest distance index if needed var itemCenterX = (rect.left + rect.right) / 2; var itemCenterY = (rect.top + rect.bottom) / 2; var distance = Math.sqrt(Math.pow(x - itemCenterX, 2) + Math.pow(y - itemCenterY, 2)); // ** 2 operator is not supported on IE11 if (distance < smallestDistance) { smallestDistance = distance; smallestDistanceIndex = index; } } } return smallestDistanceIndex; }; /** * Finds the first scrollable parent of an element. * @param {HTMLElement} element The element to start searching from. * @returns {HTMLElement | Window} The scrollable parent or the window. */ var getScrollableParent = function getScrollableParent(element) { if (!element) { return window; } var current = element; while (current) { var _a = window.getComputedStyle(current), overflowX = _a.overflowX, overflowY = _a.overflowY; // check if the element is horizontally scrollable var isHorizontallyScrollable = (overflowX === 'auto' || overflowX === 'scroll') && current.scrollWidth > current.clientWidth; // check if the element is vertically scrollable var isVerticallyScrollable = (overflowY === 'auto' || overflowY === 'scroll') && current.scrollHeight > current.clientHeight; // if it's scrollable in either direction it's a match if (isHorizontallyScrollable || isVerticallyScrollable) { return current; } current = current.parentElement; } return window; }; function arrayMoveMutable(array, fromIndex, toIndex) { var startIndex = fromIndex < 0 ? array.length + fromIndex : fromIndex; if (startIndex >= 0 && startIndex < array.length) { var endIndex = toIndex < 0 ? array.length + toIndex : toIndex; var item = array.splice(fromIndex, 1)[0]; array.splice(endIndex, 0, item); } } function arrayMove(array, fromIndex, toIndex) { var newArray = __spreadArray([], array, true); arrayMoveMutable(newArray, fromIndex, toIndex); return newArray; } var getMousePoint = function getMousePoint(e) { return { x: Number(e.clientX), y: Number(e.clientY) }; }; var getTouchPoint = function getTouchPoint(touch) { return { x: Number(touch.clientX), y: Number(touch.clientY) }; }; var getPointInContainer = function getPointInContainer(point, containerTopLeft) { return { x: point.x - containerTopLeft.x, y: point.y - containerTopLeft.y }; }; var preventDefault = function preventDefault(event) { event.preventDefault(); }; var disableContextMenu = function disableContextMenu() { window.addEventListener('contextmenu', preventDefault, { capture: true, passive: false }); }; var enableContextMenu = function enableContextMenu() { window.removeEventListener('contextmenu', preventDefault); }; var useDrag = function useDrag(_a) { var onStart = _a.onStart, onMove = _a.onMove, onEnd = _a.onEnd, _b = _a.allowDrag, allowDrag = _b === void 0 ? true : _b, containerRef = _a.containerRef, knobs = _a.knobs; // contains the top-left coordinates of the container in the window. Set on drag start and used in drag move var containerPositionRef = React.useRef({ x: 0, y: 0 }); // on touch devices, we only start the drag gesture after pressing the item 200ms. // this ref contains the timer id to be able to cancel it var handleTouchStartTimerRef = React.useRef(undefined); // on non-touch device, we don't call onStart on mouse down but on the first mouse move // we do this to let the user clicks on clickable element inside the container // this means that the drag gesture actually starts on the fist move var isFirstMoveRef = React.useRef(false); // see https://twitter.com/ValentinHervieu/status/1324407814970920968 // we do this so that the parent doesn't have to use `useCallback()` for these callbacks var callbacksRef = React.useRef({ onStart: onStart, onMove: onMove, onEnd: onEnd }); // instead of relying on hacks to know if the device is a touch device or not, // we track this using an onTouchStart listener on the document. (see https://codeburst.io/the-only-way-to-detect-touch-with-javascript-7791a3346685) var _c = React.useState(false), isTouchDevice = _c[0], setTouchDevice = _c[1]; React.useEffect(function () { callbacksRef.current = { onStart: onStart, onMove: onMove, onEnd: onEnd }; }, [onStart, onMove, onEnd]); var cancelTouchStart = function cancelTouchStart() { if (handleTouchStartTimerRef.current) { window.clearTimeout(handleTouchStartTimerRef.current); } }; var saveContainerPosition = React.useCallback(function () { if (containerRef.current) { var bounds = containerRef.current.getBoundingClientRect(); containerPositionRef.current = { x: bounds.left, y: bounds.top }; } }, [containerRef]); var onDrag = React.useCallback(function (pointInWindow) { var point = getPointInContainer(pointInWindow, containerPositionRef.current); if (callbacksRef.current.onMove) { callbacksRef.current.onMove({ pointInWindow: pointInWindow, point: point }); } }, []); var onMouseMove = React.useCallback(function (e) { // if this is the first move, we trigger the onStart logic if (isFirstMoveRef.current) { isFirstMoveRef.current = false; var pointInWindow = getMousePoint(e); var point = getPointInContainer(pointInWindow, containerPositionRef.current); if (callbacksRef.current.onStart) { callbacksRef.current.onStart({ point: point, pointInWindow: pointInWindow }); } } // otherwise, we do the normal move logic else { onDrag(getMousePoint(e)); } }, [onDrag]); var onTouchMove = React.useCallback(function (e) { if (e.cancelable) { // Prevent the whole page from scrolling e.preventDefault(); onDrag(getTouchPoint(e.touches[0])); } else { // if the event is not cancelable, it means the browser is currently scrolling // which cannot be interrupted. Thus we cancel the drag gesture. document.removeEventListener('touchmove', onTouchMove); if (callbacksRef.current.onEnd) { callbacksRef.current.onEnd(); } } }, [onDrag]); var onMouseUp = React.useCallback(function () { isFirstMoveRef.current = false; document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); if (callbacksRef.current.onEnd) { callbacksRef.current.onEnd(); } }, [onMouseMove]); var onTouchEnd = React.useCallback(function () { document.removeEventListener('touchmove', onTouchMove); document.removeEventListener('touchend', onTouchEnd); enableContextMenu(); if (callbacksRef.current.onEnd) { callbacksRef.current.onEnd(); } }, [onTouchMove]); var onMouseDown = React.useCallback(function (e) { if (e.button !== 0) { // we don't want to handle clicks other than left ones return; } if ((knobs === null || knobs === void 0 ? void 0 : knobs.length) && !knobs.find(function (knob) { return knob.contains(e.target); })) { return; } document.addEventListener('mousemove', onMouseMove); document.addEventListener('mouseup', onMouseUp); saveContainerPosition(); // mark the next move as being the first one isFirstMoveRef.current = true; }, [onMouseMove, onMouseUp, saveContainerPosition, knobs]); var handleTouchStart = React.useCallback(function (point, pointInWindow) { document.addEventListener('touchmove', onTouchMove, { capture: false, passive: false }); document.addEventListener('touchend', onTouchEnd); disableContextMenu(); if (callbacksRef.current.onStart) { callbacksRef.current.onStart({ point: point, pointInWindow: pointInWindow }); } }, [onTouchEnd, onTouchMove]); var onTouchStart = React.useCallback(function (e) { if ((knobs === null || knobs === void 0 ? void 0 : knobs.length) && !knobs.find(function (knob) { return knob.contains(e.target); })) { return; } saveContainerPosition(); var pointInWindow = getTouchPoint(e.touches[0]); var point = getPointInContainer(pointInWindow, containerPositionRef.current); // we wait 120ms to start the gesture to be sure that the user // is not trying to scroll the page handleTouchStartTimerRef.current = window.setTimeout(function () { return handleTouchStart(point, pointInWindow); }, 120); }, [handleTouchStart, saveContainerPosition, knobs]); var detectTouchDevice = React.useCallback(function () { setTouchDevice(true); document.removeEventListener('touchstart', detectTouchDevice); }, []); // if the user is scrolling on mobile, we cancel the drag gesture var touchScrollListener = React.useCallback(function () { cancelTouchStart(); }, []); React.useLayoutEffect(function () { if (isTouchDevice) { var container_1 = containerRef.current; if (allowDrag) { container_1 === null || container_1 === void 0 ? void 0 : container_1.addEventListener('touchstart', onTouchStart, { capture: true, passive: false }); // we are adding this touchmove listener to cancel drag if user is scrolling // however, it's also important to have a touchmove listener always set // with non-capture and non-passive option to prevent an issue on Safari // with e.preventDefault (https://github.com/atlassian/react-beautiful-dnd/issues/1374) document.addEventListener('touchmove', touchScrollListener, { capture: false, passive: false }); document.addEventListener('touchend', touchScrollListener, { capture: false, passive: false }); } return function () { container_1 === null || container_1 === void 0 ? void 0 : container_1.removeEventListener('touchstart', onTouchStart, { capture: true }); document.removeEventListener('touchmove', touchScrollListener, { capture: false }); document.removeEventListener('touchend', touchScrollListener, { capture: false }); document.removeEventListener('touchmove', onTouchMove); document.removeEventListener('touchend', onTouchEnd); enableContextMenu(); cancelTouchStart(); }; } // if non-touch device document.addEventListener('touchstart', detectTouchDevice); return function () { document.removeEventListener('touchstart', detectTouchDevice); document.removeEventListener('mousemove', onMouseMove); document.removeEventListener('mouseup', onMouseUp); }; }, [isTouchDevice, allowDrag, detectTouchDevice, onMouseMove, onTouchMove, touchScrollListener, onTouchEnd, onMouseUp, containerRef, onTouchStart]); // on touch devices, we cannot attach the onTouchStart directly via React: // Touch handlers must be added with {passive: false} to be cancelable. // https://developers.google.com/web/updates/2017/01/scrolling-intervention return isTouchDevice ? {} : { onMouseDown: onMouseDown }; }; var useDropTarget = function useDropTarget(content) { var dropTargetRef = React.useRef(null); if (!content) { return {}; } var show = function show(sourceRect) { if (dropTargetRef.current) { dropTargetRef.current.style.width = "".concat(sourceRect.width, "px"); dropTargetRef.current.style.height = "".concat(sourceRect.height, "px"); dropTargetRef.current.style.opacity = '1'; dropTargetRef.current.style.visibility = 'visible'; } }; var hide = function hide() { if (dropTargetRef.current) { dropTargetRef.current.style.opacity = '0'; dropTargetRef.current.style.visibility = 'hidden'; } }; var setPosition = function setPosition(index, itemsRect, lockAxis) { if (dropTargetRef.current) { var sourceRect = itemsRect[index]; var newX = lockAxis === 'y' ? sourceRect.left : itemsRect[index].left; var newY = lockAxis === 'x' ? sourceRect.top : itemsRect[index].top; dropTargetRef.current.style.transform = "translate3d(".concat(newX, "px, ").concat(newY, "px, 0px)"); } }; var DropTargetWrapper = function DropTargetWrapper() { return React.createElement("div", { ref: dropTargetRef, "aria-hidden": true, style: { opacity: 0, visibility: 'hidden', position: 'fixed', top: 0, left: 0, pointerEvents: 'none' } }, content); }; return { show: show, hide: hide, setPosition: setPosition, render: DropTargetWrapper }; }; var DEFAULT_CONTAINER_TAG = 'div'; var SortableListContext = React.createContext(undefined); var SortableList = function SortableList(_a) { var _b; var children = _a.children, _c = _a.allowDrag, allowDrag = _c === void 0 ? true : _c, onSortStart = _a.onSortStart, onSortMove = _a.onSortMove, onSortEnd = _a.onSortEnd, draggedItemClassName = _a.draggedItemClassName, as = _a.as, lockAxis = _a.lockAxis, customHolderRef = _a.customHolderRef, dropTarget = _a.dropTarget, _d = _a.autoScroll, autoScroll = _d === void 0 ? false : _d, rest = __rest(_a, ["children", "allowDrag", "onSortStart", "onSortMove", "onSortEnd", "draggedItemClassName", "as", "lockAxis", "customHolderRef", "dropTarget", "autoScroll"]); // this array contains the elements than can be sorted (wrapped inside SortableItem) var itemsRef = React.useRef([]); // this array contains the coordinates of each sortable element (only computed on dragStart and used in dragMove for perf reason) var itemsRect = React.useRef([]); // Hold all registered knobs var knobs = React.useRef([]); // contains the container element var containerRef = React.useRef(null); // contains the target element (copy of the source element) var targetRef = React.useRef(null); // contains the index in the itemsRef array of the element being dragged var sourceIndexRef = React.useRef(undefined); // contains the index in the itemsRef of the element to be exchanged with the source item var lastTargetIndexRef = React.useRef(undefined); // contains the offset point where the initial drag occurred to be used when dragging the item var offsetPointRef = React.useRef({ x: 0, y: 0 }); // contains the dropTarget logic var dropTargetLogic = useDropTarget(dropTarget); // contains the original opacity of the sorted item in order te restore it correctly var sourceOpacityRef = React.useRef('1'); // contains the speed at which the container scrolls horizontally or vertically when auto scrolling var scrollSpeedRef = React.useRef({ x: 0, y: 0 }); // contains the auto scroll animation var scrollAnimationRef = React.useRef(null); // contains the scrollable list parent (element or window) var scrollContainerRef = React.useRef(null); // contains the horizontal and vertical scroll positions of the container var initialScrollRef = React.useRef({ x: 0, y: 0 }); // auto scroll method var autoScrolling = React.useCallback(function () { var scroller = scrollContainerRef.current; var scrollSpeed = scrollSpeedRef.current; if (scrollSpeed.x === 0 && scrollSpeed.y === 0 || !scroller) { if (scrollAnimationRef.current) { cancelAnimationFrame(scrollAnimationRef.current); scrollAnimationRef.current = null; } return; } // handle both window and element scrolling if (scroller instanceof Window) { scroller.scrollBy(scrollSpeed.x, scrollSpeed.y); } else if (scroller instanceof HTMLElement) { scroller.scrollTop += scrollSpeed.y; scroller.scrollLeft += scrollSpeed.x; } scrollAnimationRef.current = requestAnimationFrame(autoScrolling); }, []); React.useEffect(function () { var holder = (customHolderRef === null || customHolderRef === void 0 ? void 0 : customHolderRef.current) || document.body; return function () { // cleanup the target element from the DOM when SortableList in unmounted if (targetRef.current) { holder.removeChild(targetRef.current); } }; }, [customHolderRef]); var updateTargetPosition = function updateTargetPosition(position) { if (targetRef.current && sourceIndexRef.current !== undefined) { var offset = offsetPointRef.current; var sourceRect = itemsRect.current[sourceIndexRef.current]; var newX = lockAxis === 'y' ? sourceRect.left : position.x - offset.x; var newY = lockAxis === 'x' ? sourceRect.top : position.y - offset.y; // we use `translate3d` to force using the GPU if available targetRef.current.style.transform = "translate3d(".concat(newX, "px, ").concat(newY, "px, 0px)"); } }; var copyItem = React.useCallback(function (sourceIndex) { if (!containerRef.current) { return; } var source = itemsRef.current[sourceIndex]; var sourceRect = itemsRect.current[sourceIndex]; var copy = source.cloneNode(true); // added the "dragged" class name if (draggedItemClassName) { draggedItemClassName.split(' ').forEach(function (c) { return copy.classList.add(c); }); } // we ensure the copy has the same size than the source element copy.style.width = "".concat(sourceRect.width, "px"); copy.style.height = "".concat(sourceRect.height, "px"); // we place the target starting position to the top left of the window // it will then be moved relatively using `transform: translate3d()` copy.style.position = 'fixed'; copy.style.margin = '0'; copy.style.top = '0'; copy.style.left = '0'; var sourceCanvases = source.querySelectorAll('canvas'); copy.querySelectorAll('canvas').forEach(function (canvas, index) { var _a; (_a = canvas.getContext('2d')) === null || _a === void 0 ? void 0 : _a.drawImage(sourceCanvases[index], 0, 0); }); var holder = (customHolderRef === null || customHolderRef === void 0 ? void 0 : customHolderRef.current) || document.body; holder.appendChild(copy); targetRef.current = copy; }, [customHolderRef, draggedItemClassName]); var listeners = useDrag({ allowDrag: allowDrag, containerRef: containerRef, knobs: knobs.current, onStart: function onStart(_a) { var _b, _c; var pointInWindow = _a.pointInWindow; if (!containerRef.current) { return; } // auto scrolling of the container if (autoScroll) { var scroller = getScrollableParent(containerRef.current); scrollContainerRef.current = scroller; // record the starting scroll position to calculate the scroll delta later // record the starting scroll position for both axes if (scroller instanceof HTMLElement) { initialScrollRef.current = { y: scroller.scrollTop, x: scroller.scrollLeft }; } else { initialScrollRef.current = { y: scroller.scrollY, x: scroller.scrollX }; } } itemsRect.current = itemsRef.current.map(function (item) { return item.getBoundingClientRect(); }); var sourceIndex = findItemIndexAtPosition(pointInWindow, itemsRect.current); // if we are not starting the drag gesture on a SortableItem, we exit early if (sourceIndex === -1) { return; } // saving the index of the item being dragged sourceIndexRef.current = sourceIndex; // let the parent know that sort started if (onSortStart) { onSortStart(); } // the item being dragged is copied to the document body and will be used as the target copyItem(sourceIndex); // hide source during the drag gesture (and store original opacity) var source = itemsRef.current[sourceIndex]; sourceOpacityRef.current = (_b = source.style.opacity) !== null && _b !== void 0 ? _b : '1'; source.style.opacity = '0'; source.style.visibility = 'hidden'; // get the offset between the source item's window position relative to the point in window var sourceRect = source.getBoundingClientRect(); offsetPointRef.current = { x: pointInWindow.x - sourceRect.left, y: pointInWindow.y - sourceRect.top }; // set the initial position of the cloned item updateTargetPosition(pointInWindow); (_c = dropTargetLogic.show) === null || _c === void 0 ? void 0 : _c.call(dropTargetLogic, sourceRect); // Adds a nice little physical feedback if (window.navigator.vibrate) { window.navigator.vibrate(100); } }, onMove: function onMove(_a) { var _b; var pointInWindow = _a.pointInWindow; if (!containerRef.current) { return; } updateTargetPosition(pointInWindow); var sourceIndex = sourceIndexRef.current; // if there is no source, we exit early (happened when drag gesture was started outside a SortableItem) if (sourceIndex === undefined) { return; } if (autoScroll) { var scroller = scrollContainerRef.current; if (scroller) { // auto scroll trigger logic point var SCROLL_THRESHOLD = 60; var MAX_SCROLL_SPEED = 15; var pointerX = pointInWindow.x, pointerY = pointInWindow.y; // reset speed before recalculating scrollSpeedRef.current = { x: 0, y: 0 }; var scrollerRect = void 0; if (scroller instanceof Window) { scrollerRect = { top: 0, bottom: scroller.innerHeight, left: 0, right: scroller.innerWidth }; } else { scrollerRect = scroller.getBoundingClientRect(); } // vertical scroll logic (if not locked) if (lockAxis !== 'x') { if (pointerY < scrollerRect.top + SCROLL_THRESHOLD && pointerY >= scrollerRect.top) { var proximity = scrollerRect.top + SCROLL_THRESHOLD - pointerY; scrollSpeedRef.current.y = -MAX_SCROLL_SPEED * (proximity / SCROLL_THRESHOLD); } else if (pointerY > scrollerRect.bottom - SCROLL_THRESHOLD && pointerY <= scrollerRect.bottom) { var proximity = pointerY - (scrollerRect.bottom - SCROLL_THRESHOLD); scrollSpeedRef.current.y = MAX_SCROLL_SPEED * (proximity / SCROLL_THRESHOLD); } } // horizontal scroll logic (if not locked) if (lockAxis !== 'y') { if (pointerX < scrollerRect.left + SCROLL_THRESHOLD && pointerX >= scrollerRect.left) { var proximity = scrollerRect.left + SCROLL_THRESHOLD - pointerX; scrollSpeedRef.current.x = -MAX_SCROLL_SPEED * (proximity / SCROLL_THRESHOLD); } else if (pointerX > scrollerRect.right - SCROLL_THRESHOLD && pointerX <= scrollerRect.right) { var proximity = pointerX - (scrollerRect.right - SCROLL_THRESHOLD); scrollSpeedRef.current.x = MAX_SCROLL_SPEED * (proximity / SCROLL_THRESHOLD); } } // start animation if speed is not 0 if ((scrollSpeedRef.current.x !== 0 || scrollSpeedRef.current.y !== 0) && !scrollAnimationRef.current) { scrollAnimationRef.current = requestAnimationFrame(autoScrolling); } } } // drop-point correction for both axes var scrollDelta = { x: 0, y: 0 }; if (autoScroll && scrollContainerRef.current) { var scroller = scrollContainerRef.current; if (scroller instanceof HTMLElement) { scrollDelta = { y: scroller.scrollTop - initialScrollRef.current.y, x: scroller.scrollLeft - initialScrollRef.current.x }; } else if (scroller instanceof Window) { scrollDelta = { y: scroller.scrollY - initialScrollRef.current.y, x: scroller.scrollX - initialScrollRef.current.x }; } } var sourceRect = itemsRect.current[sourceIndex]; var targetPoint = { x: (lockAxis === 'y' ? sourceRect.left : pointInWindow.x) + scrollDelta.x, y: (lockAxis === 'x' ? sourceRect.top : pointInWindow.y) + scrollDelta.y }; var targetIndex = findItemIndexAtPosition(targetPoint, itemsRect.current, { fallbackToClosest: true }); // if not target detected, we don't need to update other items' position if (targetIndex === -1) { return; } // if targetIndex changed and last target index is set we can let the parent know the new position if (onSortMove && lastTargetIndexRef.current !== undefined && lastTargetIndexRef.current !== targetIndex) { onSortMove(targetIndex); } // we keep track of the last target index (to be passed to the onSortEnd callback) lastTargetIndexRef.current = targetIndex; var isMovingRight = sourceIndex < targetIndex; // in this loop, we go over each sortable item and see if we need to update their position for (var index = 0; index < itemsRef.current.length; index += 1) { var currentItem = itemsRef.current[index]; var currentItemRect = itemsRect.current[index]; // if current index is between sourceIndex and targetIndex, we need to translate them if (isMovingRight && index >= sourceIndex && index <= targetIndex || !isMovingRight && index >= targetIndex && index <= sourceIndex) { // we need to move the item to the previous or next item position var nextItemRects = itemsRect.current[isMovingRight ? index - 1 : index + 1]; if (nextItemRects) { var translateX = nextItemRects.left - currentItemRect.left; var translateY = nextItemRects.top - currentItemRect.top; // we use `translate3d` to force using the GPU if available currentItem.style.transform = "translate3d(".concat(translateX, "px, ").concat(translateY, "px, 0px)"); } } // otherwise, the item should be at its original position else { currentItem.style.transform = 'translate3d(0,0,0)'; } // we want the translation to be animated currentItem.style.transitionDuration = '300ms'; } (_b = dropTargetLogic.setPosition) === null || _b === void 0 ? void 0 : _b.call(dropTargetLogic, lastTargetIndexRef.current, itemsRect.current, lockAxis); }, onEnd: function onEnd() { var _a; // reset auto scroll variables if (autoScroll) { // reset the ref that holds the scrollable container scrollContainerRef.current = null; // reset the scroll speed scrollSpeedRef.current = { x: 0, y: 0 }; // cancel any ongoing animation frame loop if (scrollAnimationRef.current) { cancelAnimationFrame(scrollAnimationRef.current); scrollAnimationRef.current = null; } } // we reset all items translations (the parent is expected to sort the items in the onSortEnd callback) for (var index = 0; index < itemsRef.current.length; index += 1) { var currentItem = itemsRef.current[index]; currentItem.style.transform = ''; currentItem.style.transitionDuration = ''; } var sourceIndex = sourceIndexRef.current; if (sourceIndex !== undefined) { // show the source item again var source = itemsRef.current[sourceIndex]; if (source) { source.style.opacity = sourceOpacityRef.current; source.style.visibility = ''; } var targetIndex = lastTargetIndexRef.current; if (targetIndex !== undefined) { if (sourceIndex !== targetIndex) { // sort our internal items array itemsRef.current = arrayMove(itemsRef.current, sourceIndex, targetIndex); // let the parent know onSortEnd(sourceIndex, targetIndex); } } } // reset internal state refs sourceIndexRef.current = undefined; lastTargetIndexRef.current = undefined; (_a = dropTargetLogic.hide) === null || _a === void 0 ? void 0 : _a.call(dropTargetLogic); // cleanup the target element from the DOM if (targetRef.current) { var holder = (customHolderRef === null || customHolderRef === void 0 ? void 0 : customHolderRef.current) || document.body; holder.removeChild(targetRef.current); targetRef.current = null; } } }); var registerItem = React.useCallback(function (item) { itemsRef.current.push(item); }, []); var removeItem = React.useCallback(function (item) { var index = itemsRef.current.indexOf(item); if (index !== -1) { itemsRef.current.splice(index, 1); } }, []); var registerKnob = React.useCallback(function (item) { knobs.current.push(item); }, []); var removeKnob = React.useCallback(function (item) { var index = knobs.current.indexOf(item); if (index !== -1) { knobs.current.splice(index, 1); } }, []); // we need to memoize the context to avoid re-rendering every children of the context provider // when not needed var context = React.useMemo(function () { return { registerItem: registerItem, removeItem: removeItem, registerKnob: registerKnob, removeKnob: removeKnob }; }, [registerItem, removeItem, registerKnob, removeKnob]); return React.createElement(as || DEFAULT_CONTAINER_TAG, __assign(__assign(__assign({}, allowDrag ? listeners : {}), rest), { ref: containerRef }), React.createElement(SortableListContext.Provider, { value: context }, children, (_b = dropTargetLogic.render) === null || _b === void 0 ? void 0 : _b.call(dropTargetLogic))); }; /** * SortableItem only adds a ref to its children so that we can register it to the main Sortable */ var SortableItem = function SortableItem(_a) { var children = _a.children; var context = React.useContext(SortableListContext); if (!context) { throw new Error('SortableItem must be a child of SortableList'); } var registerItem = context.registerItem, removeItem = context.removeItem; var elementRef = React.useRef(null); React.useEffect(function () { var currentItem = elementRef.current; if (currentItem) { registerItem(currentItem); } return function () { if (currentItem) { removeItem(currentItem); } }; // if the children changes, we want to re-register the DOM node }, [registerItem, removeItem, children]); return React.cloneElement(children, { ref: elementRef }); }; var SortableKnob = function SortableKnob(_a) { var children = _a.children; var context = React.useContext(SortableListContext); if (!context) { throw new Error('SortableKnob must be a child of SortableList'); } var registerKnob = context.registerKnob, removeKnob = context.removeKnob; var elementRef = React.useRef(null); React.useEffect(function () { var currentItem = elementRef.current; if (currentItem) { registerKnob(currentItem); } return function () { if (currentItem) { removeKnob(currentItem); } }; // if the children changes, we want to re-register the DOM node }, [registerKnob, removeKnob, children]); return React.cloneElement(children, { ref: elementRef }); }; export { SortableItem, SortableKnob, SortableList as default }; //# sourceMappingURL=index.module.js.map