UNPKG

react-selectable-box

Version:

A React component used hooks that allows you to select elements in the drag area using the mouse

337 lines (335 loc) 13.6 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/Selectable.tsx var Selectable_exports = {}; __export(Selectable_exports, { default: () => Selectable_default }); module.exports = __toCommonJS(Selectable_exports); var import_react = __toESM(require("react")); var import_react_dom = require("react-dom"); var import_context = require("./context"); var import_useContainer = __toESM(require("./hooks/useContainer")); var import_useEvent = __toESM(require("./hooks/useEvent")); var import_useLatest = __toESM(require("./hooks/useLatest")); var import_useScroll = __toESM(require("./hooks/useScroll")); var import_utils = require("./utils"); function defaultCompareFn(a, b) { return a === b; } function Selectable({ value, virtualItems, disabled, mode = "add", children, selectStartRange = "all", scrollSpeed, scrollContainer: propsScrollContainer, dragContainer: propsDragContainer, boxStyle, boxClassName, compareFn = defaultCompareFn, allowTextSelection, onStart, onEnd }, ref) { var _a, _b; if (process.env.NODE_ENV === "development" && selectStartRange !== "all") { console.warn( "[Selectable] selectStartRange is deprecated, please use onStart to prevent selection start instead." ); } const [isDragging, setIsDragging] = (0, import_react.useState)(false); const [startCoords, setStartCoords] = (0, import_react.useState)({ x: 0, y: 0 }); const [moveCoords, setMoveCoords] = (0, import_react.useState)({ x: 0, y: 0 }); const selectingValue = (0, import_react.useRef)([]); const [startTarget, setStartTarget] = (0, import_react.useState)(null); const startInside = (0, import_react.useRef)(false); const moveClient = (0, import_react.useRef)({ x: 0, y: 0 }); const boxRef = (0, import_react.useRef)(null); const unmountItemsInfo = (0, import_react.useRef)(/* @__PURE__ */ new Map()); const scrollInfo = (0, import_react.useRef)({ scrollTop: 0, scrollLeft: 0 }); const [isCanceled, setIsCanceled] = (0, import_react.useState)(false); const getInnerScrollContainer = typeof propsScrollContainer === "function" ? propsScrollContainer : (_a = propsScrollContainer == null ? void 0 : propsScrollContainer.inner) == null ? void 0 : _a.getContainer; const innerScrollContainer = (0, import_useContainer.default)(getInnerScrollContainer); const outerScrollContainer = (0, import_useContainer.default)( typeof propsScrollContainer === "object" ? (_b = propsScrollContainer == null ? void 0 : propsScrollContainer.outer) == null ? void 0 : _b.getContainer : void 0, false ); const dragContainer = (0, import_useContainer.default)(propsDragContainer || getInnerScrollContainer); const { smoothScroll, cancelScroll } = (0, import_useScroll.default)(scrollSpeed, propsScrollContainer); const startCoordsRef = (0, import_useLatest.default)(startCoords); const isDraggingRef = (0, import_useLatest.default)(isDragging); const selectStartRangeRef = (0, import_useLatest.default)(selectStartRange); const allowTextSelectionRef = (0, import_useLatest.default)(allowTextSelection); const top = Math.max(0, Math.min(startCoords.y, moveCoords.y)); const left = Math.max(0, Math.min(startCoords.x, moveCoords.x)); const width = isDragging ? Math.abs(startCoords.x - Math.max(0, moveCoords.x)) : 0; const height = isDragging ? Math.abs(startCoords.y - Math.max(0, moveCoords.y)) : 0; const boxPosition = (0, import_react.useMemo)(() => ({ top, left, width, height }), [top, left, width, height]); const virtual = !!virtualItems; import_react.default.useImperativeHandle(ref, () => ({ cancel: () => { setIsCanceled(true); setIsDragging(false); } })); const handleStart = (0, import_useEvent.default)((event) => { return onStart == null ? void 0 : onStart(event); }); const handleEnd = (0, import_useEvent.default)(() => { if (onEnd) { if (virtual) { unmountItemsInfo.current.forEach((info, item) => { if (virtualItems.some((i) => compareFn(i, item))) { const isInRange = (0, import_utils.checkInRange)( info.rule, { width: info.rect.width, height: info.rect.height, top: info.rect.top + info.scrollTop - scrollInfo.current.scrollTop, left: info.rect.left + info.scrollLeft - scrollInfo.current.scrollLeft }, innerScrollContainer, boxPosition, boxRef ); if (isInRange && !info.disabled) { selectingValue.current.push(item); } else { selectingValue.current = selectingValue.current.filter((i) => !compareFn(i, item)); } } }); } const added = []; const removed = []; selectingValue.current.forEach((i) => { if (value == null ? void 0 : value.some((val) => compareFn(val, i))) { if (mode === "remove" || mode === "reverse") { removed.push(i); } } else { if (mode === "add" || mode === "reverse") { added.push(i); } } }); onEnd(selectingValue.current, { added, removed }); } }); (0, import_react.useEffect)(() => { let isMouseDowning = false; let scrollContainerOriginPosition = ""; const reset = () => { setIsDragging(false); setIsCanceled(false); setStartTarget(null); isMouseDowning = false; startInside.current = false; selectingValue.current = []; }; if (disabled || !innerScrollContainer || !dragContainer || isCanceled) { reset(); return; } const onMouseMove = (e) => { if (isMouseDowning) { if (window.TouchEvent && e instanceof TouchEvent) { e.preventDefault(); } const { clientX, clientY } = (0, import_utils.getClientXY)(e); moveClient.current = { x: clientX, y: clientY }; const { left: left2, top: top2 } = innerScrollContainer.getBoundingClientRect(); const x = Math.min( clientX - left2 + innerScrollContainer.scrollLeft, innerScrollContainer.scrollWidth ); const y = Math.min( clientY - top2 + innerScrollContainer.scrollTop, innerScrollContainer.scrollHeight ); setMoveCoords({ x, y }); smoothScroll(e); if (!isDraggingRef.current) { let shouldDraggingStart = true; if (selectStartRangeRef.current === "outside") { shouldDraggingStart = !startInside.current; } else if (selectStartRangeRef.current === "inside") { shouldDraggingStart = startInside.current; } const boxWidth = Math.abs(startCoordsRef.current.x - x); const boxHeight = Math.abs(startCoordsRef.current.y - y); if (shouldDraggingStart && (boxWidth > 1 || boxHeight > 1)) { const shouldStart = handleStart(e); if (shouldStart === false) { reset(); } else { setIsDragging(true); scrollContainerOriginPosition = getComputedStyle(innerScrollContainer).position; if (innerScrollContainer !== document.body && scrollContainerOriginPosition === "static") { innerScrollContainer.style.position = "relative"; } } } } } }; const innerScrollListenerElement = innerScrollContainer === document.body ? document : innerScrollContainer; const outerScrollListenerElement = outerScrollContainer === document.body ? document : outerScrollContainer; const onScroll = (e) => { const target = e.target; scrollInfo.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft }; if (isDraggingRef.current) { const containerRect = innerScrollContainer.getBoundingClientRect(); const x = Math.min( moveClient.current.x - containerRect.left + innerScrollContainer.scrollLeft, innerScrollContainer.scrollWidth ); const y = Math.min( moveClient.current.y - containerRect.top + innerScrollContainer.scrollTop, innerScrollContainer.scrollHeight ); setMoveCoords({ x, y }); } }; const onOuterScroll = () => { onScroll({ target: innerScrollContainer }); }; const onMouseUp = () => { window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); window.removeEventListener("touchmove", onMouseMove); window.removeEventListener("touchend", onMouseUp); if (isDraggingRef.current) { innerScrollContainer.style.position = scrollContainerOriginPosition; cancelScroll(); handleEnd(); } reset(); }; const onMouseDown = (e) => { const isMouseEvent = e instanceof MouseEvent; if (isMouseEvent && e.button !== 0) { return; } if (isMouseEvent && !allowTextSelectionRef.current) { e.preventDefault(); } isMouseDowning = true; if (selectStartRangeRef.current !== "all") { setStartTarget(e.target); } const { clientX, clientY } = (0, import_utils.getClientXY)(e); const { left: left2, top: top2 } = innerScrollContainer.getBoundingClientRect(); setStartCoords({ x: clientX - left2 + innerScrollContainer.scrollLeft, y: clientY - top2 + innerScrollContainer.scrollTop }); window.addEventListener("mousemove", onMouseMove, { passive: false }); window.addEventListener("mouseup", onMouseUp); window.addEventListener("touchmove", onMouseMove, { passive: false }); window.addEventListener("touchend", onMouseUp); }; dragContainer.addEventListener("mousedown", onMouseDown); dragContainer.addEventListener("touchstart", onMouseDown); innerScrollListenerElement.addEventListener("scroll", onScroll); outerScrollListenerElement == null ? void 0 : outerScrollListenerElement.addEventListener("scroll", onOuterScroll); return () => { if (scrollContainerOriginPosition) { innerScrollContainer.style.position = scrollContainerOriginPosition; } cancelScroll(); dragContainer.removeEventListener("mousedown", onMouseDown); dragContainer.removeEventListener("touchstart", onMouseDown); innerScrollListenerElement.removeEventListener("scroll", onScroll); outerScrollListenerElement == null ? void 0 : outerScrollListenerElement.removeEventListener("scroll", onOuterScroll); window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); window.removeEventListener("touchmove", onMouseMove); window.removeEventListener("touchend", onMouseUp); }; }, [disabled, innerScrollContainer, outerScrollContainer, dragContainer, isCanceled]); const contextValue = (0, import_react.useMemo)( () => ({ value, selectingValue, isDragging, boxPosition, mode, innerScrollContainer, startTarget, startInside, unmountItemsInfo, virtual, boxRef, compareFn }), [ value, isDragging, boxPosition, mode, innerScrollContainer, startTarget, unmountItemsInfo, virtual, boxRef, compareFn ] ); return /* @__PURE__ */ import_react.default.createElement(import_context.SelectableContext.Provider, { value: contextValue }, children, isDragging && innerScrollContainer && (0, import_react_dom.createPortal)( /* @__PURE__ */ import_react.default.createElement( "div", { ref: boxRef, className: boxClassName, style: { position: "absolute", zIndex: 9999, top: 0, left: 0, transform: `translate(${left}px, ${top}px)`, width, height, backgroundColor: "rgba(22, 119, 255, 0.3)", ...boxStyle } } ), innerScrollContainer )); } var Selectable_default = import_react.default.forwardRef(Selectable);