Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

react-virtual-drag-list

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

react-virtual-drag-list - npm Package Compare versions

Comparing version 1.0.6 to 2.0.0

dist/children.d.ts

1185

dist/index.js
/*!
* react-virtual-drag-list v1.0.5
* react-virtual-drag-list v2.0.0
* open source under the MIT license

@@ -8,826 +8,423 @@ * https://github.com/mfuu/react-virtual-drag-list#readme

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react')) :
typeof define === 'function' && define.amd ? define(['react'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.virtualDragList = factory(global.React));
})(this, (function (React) { 'use strict';
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('js-draggable-list')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'js-draggable-list'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.virtualDragList = {}, global.React, global.Draggable));
})(this, (function (exports, React, Draggable) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
enumerableOnly && (symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
})), keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = null != arguments[i] ? arguments[i] : {};
i % 2 ? ownKeys(Object(source), !0).forEach(function (key) {
_defineProperty(target, key, source[key]);
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
function _interopNamespace(e) {
if (e && e.__esModule) return e;
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
return target;
n["default"] = e;
return Object.freeze(n);
}
function _typeof(obj) {
"@babel/helpers - typeof";
var React__namespace = /*#__PURE__*/_interopNamespace(React);
var Draggable__default = /*#__PURE__*/_interopDefaultLegacy(Draggable);
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
return typeof obj;
} : function (obj) {
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
}, _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
function Observer(props) {
const { uniqueKey, children, onSizeChange } = props;
const elementRef = React__namespace.useRef(null);
const isRenderProps = typeof children === 'function';
const mergedChildren = isRenderProps ? children(elementRef) : children;
React__namespace.useLayoutEffect(() => {
let observer;
if (typeof ResizeObserver !== undefined) {
observer = new ResizeObserver(() => {
const size = elementRef.current.clientHeight;
onSizeChange && onSizeChange(size, uniqueKey);
});
elementRef.current && observer.observe(elementRef.current);
}
return () => {
if (observer) {
observer.disconnect();
observer = null;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [elementRef]);
return React__namespace.cloneElement(mergedChildren, {
ref: elementRef,
});
} else {
obj[key] = value;
}
return obj;
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();
function Item(props) {
const { children, uniqueKey, itemClass, itemStyle } = props;
const { record, index, onSizeChange } = props;
return (React__namespace.createElement(Observer, { uniqueKey: uniqueKey, onSizeChange: onSizeChange },
React__namespace.createElement("div", { className: itemClass, style: itemStyle, "data-key": uniqueKey }, typeof children === 'function' ? children(record, index, uniqueKey) : children)));
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
function Slot(props) {
const { children, roleId, onSizeChange } = props;
return children ? (React__namespace.createElement(Observer, { uniqueKey: roleId, onSizeChange: onSizeChange },
React__namespace.createElement("div", { "v-role": roleId }, children))) : React__namespace.createElement(React__namespace.Fragment, null);
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
if (_i == null) return;
var _arr = [];
var _n = true;
var _d = false;
var _s, _e;
try {
for (_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
var utils = {
/**
* 防抖函数
* @param func callback
* @param delay 延迟
* @param immediate 是否立即执行
* @returns
*/
debounce(func, delay = 50, immediate = false) {
let timer;
let result;
let debounced = function (...args) {
if (timer)
clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, delay);
if (callNow)
result = func.apply(this, args);
}
else {
timer = setTimeout(() => {
func.apply(this, args);
}, delay);
}
return result;
};
debounced.prototype.cancel = function () {
clearTimeout(timer);
timer = null;
};
return debounced;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
};
return _arr;
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
// 设置动画
function animate(rect, target) {
var delay = 300;
{
var cRect = target.getBoundingClientRect();
if (rect.nodeType === 1) rect = rect.getBoundingClientRect();
setStyle(target, 'transition', 'none');
setStyle(target, 'transform', "translate3d(".concat(rect.left - cRect.left, "px, ").concat(rect.top - cRect.top, "px, 0)"));
target.offsetWidth; // 触发重绘
setStyle(target, 'transition', "all ".concat(delay, "ms"));
setStyle(target, 'transform', 'translate3d(0, 0, 0)');
clearTimeout(target.animated);
target.animated = setTimeout(function () {
setStyle(target, 'transition', '');
setStyle(target, 'transform', '');
target.animated = false;
}, delay);
}
} // 为dom添加样式
function setStyle(el, prop, val) {
var style = el && el.style;
if (style) {
if (val === void 0) {
if (document.defaultView && document.defaultView.getComputedStyle) {
val = document.defaultView.getComputedStyle(el, '');
} else if (el.currentStyle) {
val = el.currentStyle;
}
return prop === void 0 ? val : val[prop];
} else {
if (!(prop in style)) prop = '-webkit-' + prop;
style[prop] = val + (typeof val === 'string' ? '' : 'px');
}
}
}
function getUniqueKey(item, key) {
return (!Array.isArray(key) ? key.replace(/\[/g, '.').replace(/\]/g, '.').split('.') : key).reduce(function (o, k) {
return (o || {})[key];
}, item) || '';
}
function VirtualSlot(props) {
var children = props.children,
roleId = props.roleId,
onSizeChange = props.onSizeChange;
var vm = React.useRef(); // ======================= observer =======================
React.useLayoutEffect(function () {
var observer;
if ((typeof ResizeObserver === "undefined" ? "undefined" : _typeof(ResizeObserver)) !== undefined) {
observer = new ResizeObserver(function () {
var size = vm.current.clientHeight;
onSizeChange(size, roleId);
});
vm.current && observer.observe(vm.current);
}
return function () {
if (observer) {
observer.disconnect();
observer = null;
}
const CALLBACKS = { top: 'v-top', bottom: 'v-bottom', dragend: 'v-dragend' }; // 组件传入的事件回调
const STYLE = { overflow: 'hidden auto', position: 'relative' }; // 列表默认样式
const MASKIMAGE = 'linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0.1) 40%, rgba(0, 0, 0, 0.1) 98%, #FFFFFF 100%)'; // 拖拽时默认背景样式
function Virtual(props, ref) {
const { header, footer, children, dataSource = [], dataKey, keeps = 30, size = 50, height = '100%', delay = 10, dragStyle = { backgroundImage: MASKIMAGE } } = props;
// =============================== State ===============================
const [cloneList, setCloneList] = React.useState([]);
const [sizeStack, setSizeStack] = React.useState(new Map());
const dragList = React.useRef([]);
const [range, setRange] = React.useState({ start: 0, end: keeps }); // 当前可见范围
const dragRef = React.useRef(null);
const offsetRef = React.useRef(0); // 记录当前滚动高度
const virtualRef = React.useRef(null);
const groupRef = React.useRef(null); // 列表ref
const bottomRef = React.useRef(null); // 列表元素外的dom,总是存在于列表最后
// 记录顶部 | 底部高度,以及列表中每一行的高度,和列表的总高度
const calcSizeRef = React.useRef({ header: 0, footer: 0, total: 0, average: 0, fixed: 0 });
const calcTypeRef = React.useRef('INIT'); // 判断列表每一行高度是否固定
const lastIndexRef = React.useRef(0); // 记录上一次计算的 index 值
const isFixedSize = React__namespace.useMemo(() => {
return calcTypeRef.current === 'FIXED';
}, [calcTypeRef]);
React.useEffect(() => {
setCloneList(() => [...dataSource]);
dragList.current = [...dataSource];
}, [dataSource]);
// =============================== ref methods ===============================
const scrollToBottom = () => {
if (bottomRef) {
const offset = bottomRef.current.offsetTop;
virtualRef.current.scrollTop = offset;
}
setTimeout(() => {
// 第一次滚动高度可能会发生改变,如果没到底部再执行一次滚动方法
const { scrollTop, scrollHeight, clientHeight } = virtualRef.current;
if (scrollTop + clientHeight < scrollHeight)
scrollToBottom();
}, 0);
};
}, []);
return children ? /*#__PURE__*/React__default["default"].createElement("div", {
ref: vm,
"v-role": roleId
}, children) : /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null);
}
function VirtualItem(props) {
var onSizeChange = props.onSizeChange,
setDragState = props.setDragState,
handleDragEnd = props.handleDragEnd,
dragState = props.dragState;
var _props$itemProps = props.itemProps,
children = _props$itemProps.children,
dataKey = _props$itemProps.dataKey,
dataSource = _props$itemProps.dataSource,
dragStyle = _props$itemProps.dragStyle,
_props$itemProps$drag = _props$itemProps.draggable,
draggable = _props$itemProps$drag === void 0 ? true : _props$itemProps$drag;
var _props$dataProps = props.dataProps,
index = _props$dataProps.index,
record = _props$dataProps.record,
uniqueKey = _props$dataProps.uniqueKey;
var vm = React.useRef();
var mask = React.useRef(); // ======================= observer =======================
React.useLayoutEffect(function () {
var observer;
if ((typeof ResizeObserver === "undefined" ? "undefined" : _typeof(ResizeObserver)) !== undefined) {
observer = new ResizeObserver(function () {
var size = vm.current.clientHeight;
onSizeChange(size, uniqueKey);
});
vm.current && observer.observe(vm.current);
}
return function () {
if (observer) {
observer.disconnect();
observer = null;
}
React__namespace.useImperativeHandle(ref, () => ({
reset() {
virtualRef.current.scrollTop = 0;
setCloneList(() => [...dataSource]);
},
getSize(key) {
return sizeStack.get(key);
},
getScrollTop() {
return offsetRef.current;
},
scrollToIndex(index) {
if (index >= cloneList.length - 1) {
scrollToBottom();
}
else {
const offset = getOffsetByIndex(index);
virtualRef.current.scrollTop = offset;
}
},
scrollToOffset(offset) {
virtualRef.current.scrollTop = offset;
},
scrollToTop() {
virtualRef.current.scrollTop = 0;
},
scrollToBottom,
}));
// =============================== Item Key ===============================
// 获取 item 中的 dateKey 值
const getKey = React__namespace.useCallback((item) => {
return (dataKey.replace(/\[/g, '.').replace(/\]/g, '').split('.').reduce((o, k) => (o || {})[k], item)) || '';
}, [dataKey]);
// 源数据中所有的 dataKey 值
const dataKeys = React__namespace.useMemo(() => {
return cloneList.map(item => getKey(item));
}, [cloneList, getKey]);
const dataKeyLen = React__namespace.useMemo(() => {
return dataKeys.length - 1;
}, [dataKeys]);
// =============================== Scroll ===============================
const handleScroll = (e) => {
const scrollTop = Math.ceil(e.target.scrollTop);
const scrollHeight = Math.ceil(e.target.scrollHeight);
const clientHeight = Math.ceil(e.target.clientHeight);
// 如果不存在滚动元素 | 滚动高度小于0 | 超出最大滚动距离
if (!scrollHeight || scrollTop < 0 || (scrollTop + clientHeight > scrollHeight + 1))
return;
// 通过上一次滚动的距离,判断当前滚动方向
const direction = scrollTop < offsetRef.current ? 'FRONT' : 'BEHIND';
// 记录当前滚动高度
offsetRef.current = scrollTop;
// 判断当前应该触发的回调函数,滚动到顶部时触发 `v-top`,滚动到底部时触发 `v-bottom`
const callback = direction === 'FRONT' ? props[CALLBACKS.top] : props[CALLBACKS.bottom];
const scrollOvers = getScrollOvers();
if (direction === 'FRONT') {
handleScrollFront(scrollOvers);
if (!!cloneList.length && offsetRef.current <= 0)
callback && callback();
}
else if (direction === 'BEHIND') {
handleScrollBehind(scrollOvers);
if (clientHeight + scrollTop >= scrollHeight)
callback && callback();
}
};
}, []); // ======================= drag =======================
function handleOnMouseDown(e, vm) {
if (!draggable) return; // 仅设置了draggable=true的元素才可拖动
var allow = e.target.getAttribute('draggable');
if (!allow) return; // 记录初始拖拽元素
var _getTarget = getTarget(e, vm),
target = _getTarget.target,
item = _getTarget.item;
setDragState({
oldNode: target,
oldItem: item
});
setMask('init', e.clientX, e.clientY);
document.onmousemove = function (evt) {
evt.preventDefault();
setMask('move', evt.clientX, evt.clientY);
var _getTarget2 = getTarget(evt),
_getTarget2$target = _getTarget2.target,
target = _getTarget2$target === void 0 ? null : _getTarget2$target,
_getTarget2$item = _getTarget2.item,
item = _getTarget2$item === void 0 ? null : _getTarget2$item; // 如果没找到目标节点,取消拖拽事件
if (!target || !item) {
document.body.style.cursor = 'not-allowed';
return;
}
document.body.style.cursor = 'grabbing'; // 记录拖拽目标元素
setDragState({
newNode: target,
newItem: item
});
var _dragState$current = dragState.current,
oldNode = _dragState$current.oldNode,
newNode = _dragState$current.newNode,
oldItem = _dragState$current.oldItem,
newItem = _dragState$current.newItem; // 拖拽前后不一致,改变拖拽节点位置
if (oldItem != newItem) {
if (newNode && newNode.animated) return;
var oldIndex = dataSource.indexOf(oldItem);
var newIndex = dataSource.indexOf(newItem);
var oldRect = oldNode.getBoundingClientRect();
var newRect = newNode.getBoundingClientRect();
setDragState({
oldIndex: oldIndex,
newIndex: newIndex
});
if (oldIndex < newIndex) {
newNode.parentNode.insertBefore(oldNode, newNode.nextSibling);
} else {
newNode.parentNode.insertBefore(oldNode, newNode);
const handleScrollFront = (overs) => {
if (overs > range.start)
return;
const start = Math.max(overs - Math.round(keeps / 3), 0);
handleCheck(start, getEndByStart(start));
};
const handleScrollBehind = (overs) => {
if (overs < range.start + Math.round(keeps / 3))
return;
handleCheck(overs, getEndByStart(overs));
};
const handleCheck = (start, end) => {
const total = dataKeys.length;
if (total <= keeps) {
start = 0;
end = dataKeyLen;
}
animate(oldRect, oldNode);
animate(newRect, newNode);
}
else if (end - start < keeps - 1) {
start = end - keeps + 1;
}
if (range.start !== start)
setRange(() => { return { start, end }; });
};
document.onmouseup = function () {
document.onmousemove = null;
document.onmouseup = null;
setMask('destory'); // 当前拖拽位置不在允许的范围内时不需要对数组重新赋值
if (document.body.style.cursor != 'not-allowed') {
var _dragState$current2 = dragState.current,
oldItem = _dragState$current2.oldItem,
oldIndex = _dragState$current2.oldIndex,
newIndex = _dragState$current2.newIndex; // 拖拽前后不一致,数组重新赋值
if (oldIndex != newIndex) {
var newArr = _toConsumableArray(dataSource);
newArr.splice(oldIndex, 1);
newArr.splice(newIndex, 0, oldItem);
handleDragEnd(newArr);
// =============================== methods ===============================
// 获取当前滚动高度经过了多少个列表项
const getScrollOvers = () => {
// 如果有 header 插槽,需要减去 header 的高度
const { header, fixed } = calcSizeRef.current;
const offset = offsetRef.current - header;
if (offset <= 0)
return 0;
if (isFixedSize)
return Math.floor(offset / fixed);
let low = 0;
let high = dataKeys.length;
let middle = 0;
let middleOffset = 0;
while (low <= high) {
middle = low + Math.floor((high - low) / 2);
middleOffset = getOffsetByIndex(middle);
if (middleOffset === offset) {
return middle;
}
else if (middleOffset < offset) {
low = middle + 1;
}
else if (middleOffset > offset) {
high = middle - 1;
}
else {
break;
}
}
}
document.body.style.cursor = '';
return low > 0 ? --low : 0;
};
}
function setMask(type, left, top) {
if (type == 'init') {
mask.current = document.createElement('div');
for (var key in dragStyle) {
setStyle(mask.current, key, dragStyle[key]);
}
mask.current.style.position = 'fixed';
mask.current.style.left = left + 'px';
mask.current.style.top = top + 'px';
mask.current.innerHTML = vm.current.innerHTML;
document.body.appendChild(mask.current);
} else if (type == 'move') {
mask.current.style.left = left + 'px';
mask.current.style.top = top + 'px';
} else {
document.body.removeChild(mask.current);
}
}
function getTarget(e, vm) {
var value, target;
if (vm) {
target = vm;
value = target.getAttribute('data-key');
} else {
// 如果当前拖拽超出了item范围,则不允许拖拽,否则查找dataKey属性
target = e.target;
value = target.getAttribute('data-key');
if (!value) {
var path = e.path || [];
for (var i = 0; i < path.length; i++) {
target = path[i];
value = target.getAttribute('data-key');
if (value || target == document.documentElement) break;
const getOffsetByIndex = (index) => {
if (!index)
return 0;
let offset = 0;
let indexSize = 0;
for (let i = 0; i < index; i++) {
indexSize = sizeStack.get(dataKeys[i]);
offset += typeof indexSize === 'number' ? indexSize : getItemSize();
}
}
}
var item = value ? dataSource.find(function (item) {
return getUniqueKey(item, dataKey) == value;
}) : null;
return {
target: target,
item: item
lastIndexRef.current = Math.max(lastIndexRef.current, index - 1);
lastIndexRef.current = Math.min(lastIndexRef.current, dataKeyLen);
return offset;
};
} // ======================= render =======================
return /*#__PURE__*/React__default["default"].createElement("div", {
ref: vm,
className: props.itemClass,
style: props.itemStyle,
"data-key": uniqueKey,
onMouseDown: function onMouseDown(e) {
return handleOnMouseDown(e, vm.current);
}
}, typeof children === 'function' ? children(record, index, uniqueKey) : children);
}
function Virtual(props, ref) {
// ======================= props =======================
var children = props.children,
header = props.header,
footer = props.footer;
var _props$dataSource = props.dataSource,
dataSource = _props$dataSource === void 0 ? [] : _props$dataSource,
dataKey = props.dataKey,
_props$keeps = props.keeps,
keeps = _props$keeps === void 0 ? 30 : _props$keeps,
_props$size = props.size,
size = _props$size === void 0 ? 50 : _props$size,
_props$height = props.height,
height = _props$height === void 0 ? '100%' : _props$height;
var _props$dragStyle = props.dragStyle,
dragStyle = _props$dragStyle === void 0 ? {
backgroundImage: 'linear-gradient(to bottom, rgba(255, 255, 255, 0.1) 0%, rgba(0, 0, 0, 0.1) 40%, rgba(0, 0, 0, 0.1) 98%, #FFFFFF 100%)'
} : _props$dragStyle; // ======================= ref =======================
var virtualVm = React.useRef();
var bottomVm = React.useRef(); // ======================= state =======================
var _useState = React.useState(new Map()),
_useState2 = _slicedToArray(_useState, 2),
sizeStack = _useState2[0],
setSizeStack = _useState2[1];
var scrollOffset = React.useRef(0);
var lastCalcIndex = React.useRef(0);
var calcType = React.useRef('INIT');
var calcSize = React.useRef({
header: 0,
footer: 0,
average: 0,
total: 0,
fixed: 0
});
var _useState3 = React.useState({
front: 0,
behind: 0
}),
_useState4 = _slicedToArray(_useState3, 2),
padding = _useState4[0],
setPadding = _useState4[1];
var range = React.useRef({
start: 0,
end: 0
}); // ======================= usefull methods =======================
React.useImperativeHandle(ref, function () {
return {
// 通过key值获取当前行的高度
getSize: function getSize(key) {
return sizeStack.get(key);
},
// 返回当前滚动高度
getScrollTop: function getScrollTop() {
return scrollOffset.current;
},
scrollToTop: function scrollToTop() {
_scrollToTop();
},
// 滚动到最底部
scrollToBottom: function scrollToBottom() {
_scrollToBottom();
},
// 滚动到指定高度
scrollToOffset: function scrollToOffset(offset) {
_scrollToOffset(offset);
},
scrollToIndex: function scrollToIndex(index) {
_scrollToIndex(index);
}
const getEndByStart = (start) => {
const end = start + keeps;
return dataKeyLen > 0 ? Math.min(end, dataKeyLen) : end;
};
}); // 滚动到顶部
function _scrollToTop() {
virtualVm.current.scrollTop = 0;
} // 滚动到最底部
function _scrollToBottom() {
if (bottomVm) {
var offset = bottomVm.current.offsetTop;
_scrollToOffset(offset);
} // 第一次滚动高度可能会发生改变,如果没到底部再执行一次滚动方法
var _virtualVm$current = virtualVm.current,
scrollTop = _virtualVm$current.scrollTop,
scrollHeight = _virtualVm$current.scrollHeight,
clientHeight = _virtualVm$current.clientHeight;
setTimeout(function () {
if (scrollTop + clientHeight < scrollHeight) {
_scrollToBottom();
}
}, 10);
} // 滚动到指定高度
function _scrollToOffset(offset) {
virtualVm.current.scrollTop = offset;
} // 滚动到指定索引值位置
function _scrollToIndex(index) {
if (index >= dataSource.length - 1) {
_scrollToBottom();
} else {
var offset = getOffsetByIndex(index);
_scrollToOffset(offset);
}
} // ======================= init =======================
var uniqueKeys = React.useMemo(function () {
return dataSource.map(function (item) {
return getUniqueKey(item, dataKey);
});
}, [dataSource, dataKey]);
React.useLayoutEffect(function () {
if (!dataKey) return;
handleDataSourceChange();
updateSizeStack();
}, [dataSource, dataKey]); // dataSource变更后更新range
function handleDataSourceChange() {
var start = Math.max(range.current.start, 0);
updateRange(start, getEndByStart(start));
} // dataSource变更后更新缓存
function updateSizeStack() {
sizeStack.forEach(function (v, key) {
if (!uniqueKeys.includes(key)) {
sizeStack["delete"](key);
}
});
} // ======================= size observe =======================
var onItemSizeChange = function onItemSizeChange(size, key) {
setSizeStack(sizeStack.set(key, size)); // 初始为固定高度fixedSizeValue, 如果大小没有变更不做改变,如果size发生变化,认为是动态大小,去计算平均值
if (calcType.current === 'INIT') {
calcType.current = 'FIXED';
calcSize.current = _objectSpread2(_objectSpread2({}, calcSize.current), {}, {
fixed: size
});
} else if (calcType.current === 'FIXED' && calcSize.current.fixed !== size) {
calcType.current = 'DYNAMIC';
calcSize.current = _objectSpread2(_objectSpread2({}, calcSize.current), {}, {
fixed: undefined
});
}
if (calcType.current !== 'FIXED' && calcSize.current.total !== 'undefined') {
if (sizeStack.size < Math.min(keeps, uniqueKeys.length)) {
var total = _toConsumableArray(sizeStack.values()).reduce(function (acc, cur) {
return acc + cur;
}, 0);
var average = Math.round(total / sizeStack.size);
calcSize.current = _objectSpread2(_objectSpread2({}, calcSize.current), {}, {
total: total,
average: average
const getItemSize = () => {
const { fixed, average } = calcSizeRef.current;
return isFixedSize ? fixed : (average || size);
};
// ======================= size observe =======================
const onItemSizeChange = (size, key) => {
setSizeStack(sizeStack.set(key, size));
// 初始为固定高度fixedSizeValue, 如果大小没有变更不做改变,如果size发生变化,认为是动态大小,去计算平均值
if (calcTypeRef.current === 'INIT') {
calcTypeRef.current = 'FIXED';
calcSizeRef.current = { ...calcSizeRef.current, fixed: size };
}
else if (calcTypeRef.current === 'FIXED' && calcSizeRef.current.fixed !== size) {
calcTypeRef.current = 'DYNAMIC';
calcSizeRef.current = { ...calcSizeRef.current, fixed: undefined };
}
if (calcTypeRef.current !== 'FIXED' && calcSizeRef.current.total !== 'undefined') {
if (sizeStack.size < Math.min(keeps, dataKeys.length)) {
const total = [...sizeStack.values()].reduce((acc, cur) => acc + cur, 0);
const average = Math.round(total / sizeStack.size);
calcSizeRef.current = { ...calcSizeRef.current, total, average };
}
else {
calcSizeRef.current = { ...calcSizeRef.current, total: undefined };
}
}
};
const onSlotSizeChange = (size, key) => {
calcSizeRef.current[key] = size;
};
// =============================== Range ===============================
const { start, end, front, behind } = React__namespace.useMemo(() => {
const { start, end } = range;
let front;
let behind;
if (isFixedSize) {
front = calcSizeRef.current.fixed * start;
behind = calcSizeRef.current.fixed * (dataKeyLen - end);
}
else {
front = getOffsetByIndex(start);
if (lastIndexRef.current === dataKeyLen) {
behind = getOffsetByIndex(dataKeyLen) - getOffsetByIndex(end);
}
else {
behind = (dataKeyLen - end) * getItemSize();
}
}
return { front, behind, start, end };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [range, dataKeyLen]);
// =============================== init ===============================
React.useEffect(() => {
const start = Math.max(range.start, 0);
handleCheck(start, getEndByStart(start));
sizeStack.forEach((v, key) => {
if (!dataKeys.includes(key)) {
sizeStack.delete(key);
}
});
} else {
calcSize.current = _objectSpread2(_objectSpread2({}, calcSize.current), {}, {
total: undefined
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataSource]);
// =============================== drag ===============================
React.useLayoutEffect(() => {
if (!cloneList.length)
return;
if (dragRef.current)
return;
dragRef.current = new Draggable__default["default"]({
groupElement: groupRef.current,
scrollElement: virtualRef.current,
cloneElementStyle: dragStyle,
dragElement: (e) => {
if (props.dragElement) {
return props.dragElement(e, groupRef.current);
}
else {
let result = e.target;
while ([].indexOf.call(groupRef.current.children, result) < 0) {
result = result.parentNode;
}
return result;
}
},
dragEnd: (pre, cur) => {
if (!(pre && cur))
return;
if (pre.rect.top === cur.rect.top)
return;
const dragState = {
oldNode: pre.node, oldItem: null, oldIndex: null,
newNode: cur.node, newItem: null, newIndex: null
};
const oldKey = pre.node.getAttribute('data-key');
const newKey = cur.node.getAttribute('data-key');
dragList.current.forEach((el, index) => {
if (getKey(el) === oldKey) {
dragState.oldItem = el;
dragState.oldIndex = index;
}
if (getKey(el) === newKey) {
dragState.newItem = el;
dragState.newIndex = index;
}
});
const newArr = [...dragList.current];
newArr.splice(dragState.oldIndex, 1);
newArr.splice(dragState.newIndex, 0, dragState.oldItem);
setCloneList(() => [...newArr]);
dragList.current = [...newArr];
const callback = props[CALLBACKS.dragend];
callback && callback(newArr);
// eslint-disable-next-line react-hooks/exhaustive-deps
}
});
}
}
};
var onSlotSizeChange = function onSlotSizeChange(size, key) {
if (key === 'header') calcSize.current = _objectSpread2(_objectSpread2({}, calcSize.current), {}, {
header: size
});
if (key === 'footer') calcSize.current = _objectSpread2(_objectSpread2({}, calcSize.current), {}, {
footer: size
});
}; // ======================= scroll =======================
function handleScroll(e) {
var scrollTop = Math.ceil(e.target.scrollTop);
var scrollHeight = Math.ceil(e.target.scrollHeight);
var clientHeight = Math.ceil(e.target.clientHeight); // 如果不存在滚动元素 || 滚动高度小于0 || 超出最大滚动距离
if (!scrollHeight || scrollTop < 0 || scrollTop + clientHeight > scrollHeight + 1) return; // 记录上一次滚动的距离,判断当前滚动方向
var direction = scrollTop < scrollOffset.current ? 'FRONT' : 'BEHIND';
scrollOffset.current = scrollTop;
var overs = getScrollOvers(); // 滚动到顶部/底部回调
var cb = direction === 'FRONT' ? props['v-top'] : props['v-bottom'];
if (direction === 'FRONT') {
handleScrollFront(overs);
if (!!dataSource.length && scrollTop <= 0) cb && cb();
} else if (direction === 'BEHIND') {
handleScrollBehind(overs);
if (clientHeight + scrollTop >= scrollHeight) cb && cb();
}
} // 二分法查找
function getScrollOvers() {
// 如果有header插槽,需要减去header的高度
var offset = scrollOffset.current - calcSize.current.header;
if (offset <= 0) return 0;
if (isFixedType()) return Math.floor(offset / calcSize.current.fixed);
var low = 0;
var middle = 0;
var middleOffset = 0;
var high = uniqueKeys.length;
while (low <= high) {
middle = low + Math.floor((high - low) / 2);
middleOffset = getOffsetByIndex(middle);
if (middleOffset === offset) {
return middle;
} else if (middleOffset < offset) {
low = middle + 1;
} else if (middleOffset > offset) {
high = middle - 1;
} else {
break;
}
}
return low > 0 ? --low : 0;
}
function handleScrollFront(overs) {
if (overs > range.current.start) return;
var start = Math.max(overs - Math.round(keeps / 3), 0);
checkRange(start, getEndByStart(start));
}
function handleScrollBehind(overs) {
if (overs < range.current.start + Math.round(keeps / 3)) return;
checkRange(overs, getEndByStart(overs));
}
function getEndByStart(start) {
var len = getKeyLen();
var end = start + keeps;
return len > 0 ? Math.min(end, len) : end;
} // ======================= range handler =======================
function checkRange(start, end) {
var total = uniqueKeys.length;
if (total <= keeps) {
start = 0;
end = getKeyLen();
} else if (end - start < keeps - 1) {
start = end - keeps + 1;
}
if (range.current.start !== start) {
updateRange(start, end);
}
} // 更新range
function updateRange(start, end) {
range.current = {
start: start,
end: end
};
setPadding({
front: getScrollFront(),
behind: getScrollBehind()
});
}
function getScrollFront() {
if (isFixedType()) {
return calcSize.current.fixed * range.current.start;
} else {
return getOffsetByIndex(range.current.start);
}
}
function getScrollBehind() {
var last = getKeyLen();
if (isFixedType()) {
return (last - range.current.end) * calcSize.current.fixed;
}
if (lastCalcIndex.current === last) {
return getOffsetByIndex(last) - getOffsetByIndex(range.current.end);
} else {
return (last - range.current.end) * getItemSize();
}
} // 通过索引值获取滚动高度
function getOffsetByIndex(index) {
if (!index) return 0;
var offset = 0;
var indexSize = 0;
for (var i = 0; i < index; i++) {
indexSize = sizeStack.get(uniqueKeys[i]);
offset = offset + (typeof indexSize === 'number' ? indexSize : getItemSize());
}
lastCalcIndex.current = Math.max(lastCalcIndex.current, index - 1);
lastCalcIndex.current = Math.min(lastCalcIndex.current, getKeyLen());
return offset;
} // ======================= common =======================
function isFixedType() {
return calcType.current === 'FIXED';
} // 获取每一项的高度
function getItemSize() {
return isFixedType() ? calcSize.current.fixed : calcSize.current.average || size;
}
function getItemIndex(dataKey) {
return uniqueKeys.indexOf(dataKey);
} // 获取唯一值长度
function getKeyLen() {
return uniqueKeys.length - 1;
} // ======================= item state =======================
var itemProps = React.useMemo(function () {
return _objectSpread2({
dataKey: dataKey,
children: children,
dataSource: dataSource,
dragStyle: dragStyle
}, props);
}, [dataSource, dataKey, children]);
var dragState = React.useRef({
oldNode: null,
// 拖拽起始dom元素
oldItem: null,
// 拖拽起始节点数据
oldIndex: null,
// 拖拽起始节点索引
newNode: null,
// 拖拽结束目标dom元素
newItem: null,
// 拖拽结束节点数据
newIndex: null // 拖拽结束节点索引
});
var setDragState = function setDragState(state) {
dragState.current = _objectSpread2(_objectSpread2({}, dragState.current), state);
};
function handleDragEnd(arr) {
var cb = props['v-dragend'];
cb && cb(arr);
} // ======================= render =======================
return /*#__PURE__*/React__default["default"].createElement("div", {
ref: virtualVm,
style: {
height: height,
overflow: 'hidden auto',
position: 'relative'
},
onScroll: handleScroll
}, /*#__PURE__*/React__default["default"].createElement(VirtualSlot, {
children: header,
onSizeChange: onSlotSizeChange,
roleId: "header"
}), /*#__PURE__*/React__default["default"].createElement("div", {
"v-role": "content",
style: {
padding: "".concat(padding.front, "px 0px ").concat(padding.behind, "px")
}
}, dataSource.slice(range.current.start, range.current.end).map(function (item) {
var key = getUniqueKey(item, dataKey);
var dataProps = {
uniqueKey: key,
record: item,
index: getItemIndex(key)
};
return /*#__PURE__*/React__default["default"].createElement(VirtualItem, {
key: key,
dragState: dragState,
itemProps: itemProps,
dataProps: dataProps,
setDragState: setDragState,
handleDragEnd: handleDragEnd,
onSizeChange: onItemSizeChange
});
})), /*#__PURE__*/React__default["default"].createElement(VirtualSlot, {
children: footer,
onSizeChange: onSlotSizeChange,
roleId: "footer"
}), /*#__PURE__*/React__default["default"].createElement("div", {
ref: bottomVm
}));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cloneList]);
React.useEffect(() => {
return () => {
dragRef.current && dragRef.current.destroy();
};
}, []);
// ================================ Render ================================
return (React__namespace.createElement("div", { ref: virtualRef, style: { ...STYLE, height }, onScroll: utils.debounce(handleScroll, delay) },
React__namespace.createElement(Slot, { children: header, roleId: "header", onSizeChange: onSlotSizeChange }),
React__namespace.createElement("div", { ref: groupRef, "v-role": "content", "v-start": start, style: { padding: `${front}px 0 ${behind}px` } }, cloneList.slice(start, end + 1).map(item => {
const key = getKey(item);
const index = dataKeys.indexOf(key);
return (React__namespace.createElement(Item, { key: key, uniqueKey: key, children: children, record: item, index: index, onSizeChange: onItemSizeChange }));
})),
React__namespace.createElement(Slot, { children: footer, roleId: "footer", onSizeChange: onSlotSizeChange }),
React__namespace.createElement("div", { ref: bottomRef })));
}
var index = React__namespace.forwardRef(Virtual);
var index = /*#__PURE__*/React.forwardRef(Virtual);
exports.Virtual = Virtual;
exports["default"] = index;
return index;
Object.defineProperty(exports, '__esModule', { value: true });
}));
{
"name": "react-virtual-drag-list",
"version": "1.0.6",
"version": "2.0.0",
"description": "支持拖拽排序的虚拟列表组件",

@@ -36,2 +36,3 @@ "main": "dist/index.js",

"dependencies": {
"js-draggable-list": "0.0.2",
"react": "^17.0.2",

@@ -45,8 +46,12 @@ "react-dom": "^17.0.2"

"@babel/preset-react": "^7.16.7",
"@babel/preset-typescript": "^7.16.7",
"rollup": "^2.64.0",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.31.2",
"ts-loader": "^9.2.6",
"tslib": "^2.3.1",
"typescript": "^4.5.4"
}
}

@@ -76,3 +76,5 @@ <p>

| `keeps` | Number | | the number of lines rendered by the virtual scroll | 30 |
| `delay` | Number | | Delay time of debounce function | 10 |
| `draggable` | Boolean | | whether to support drag and drop. You need to specify a draggable element and set the `draggable` attribute for it | `true` |
| `dragElement` | Function | | The function that selects the dragged element, **must have a return value with a dom node**, has two parameters: e(the currently selected element), parent(the parent node of the list) | - |
| `header` | JSX.Element| | top of list | - |

@@ -113,7 +115,9 @@ | `footer` | JSX.Element| | bottom of list | - |

|------------------|-----------------|
| `reset()` | reset to initial |
| `getSize(key)` | get the height of the specified item by key value |
| `getScrollTop()` | get the current scroll height |
| `scrollToBottom()` | scroll to the bottom of the list |
| `scrollToTop()` | scroll to the top of the list |
| `scrollToOffset(offset)` | scroll to the specified height |
| `scrollToIndex(index)` | scroll to the specified index value |
| `getSize(key)` | get the height of the specified item by key value |
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc