UNPKG

react-selectable-box

Version:

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

323 lines (321 loc) 12.4 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_useMergedState = __toESM(require("./hooks/useMergedState")); var import_useScroll = __toESM(require("./hooks/useScroll")); var import_utils = require("./utils"); function defaultCompareFn(a, b) { return a === b; } function Selectable({ defaultValue, value: propsValue, items, disabled, mode = "add", children, selectStartRange = "all", scrollSpeed, getContainer, scrollContainer: propsScrollContainer, dragContainer: propsDragContainer, boxStyle, boxClassName, compareFn = defaultCompareFn, onStart, onEnd }, ref) { 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 [value, setValue] = (0, import_useMergedState.default)(defaultValue || [], { value: propsValue }); 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 scrollContainer = (0, import_useContainer.default)(propsScrollContainer || getContainer); const dragContainer = (0, import_useContainer.default)(propsDragContainer || propsScrollContainer || getContainer); const { smoothScroll, cancelScroll } = (0, import_useScroll.default)(scrollSpeed); const startCoordsRef = (0, import_useLatest.default)(startCoords); const isDraggingRef = (0, import_useLatest.default)(isDragging); const selectStartRangeRef = (0, import_useLatest.default)(selectStartRange); 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 = !!items; if (process.env.NODE_ENV === "development" && getContainer) { console.error( "[react-selectable-box]: getContainer will be deprecated in the future, use scrollContainer instead" ); } import_react.default.useImperativeHandle(ref, () => ({ cancel: () => { setIsCanceled(true); setIsDragging(false); } })); const handleStart = (0, import_useEvent.default)((event) => { onStart == null ? void 0 : onStart(event); }); const handleEnd = (0, import_useEvent.default)(() => { if (onEnd) { if (virtual) { unmountItemsInfo.current.forEach((info, item) => { if (items.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 }, scrollContainer, 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 || !scrollContainer || !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 } = scrollContainer.getBoundingClientRect(); const x = Math.min( clientX - left2 + scrollContainer.scrollLeft, scrollContainer.scrollWidth ); const y = Math.min(clientY - top2 + scrollContainer.scrollTop, scrollContainer.scrollHeight); setMoveCoords({ x, y }); smoothScroll(e, scrollContainer); 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)) { setIsDragging(true); scrollContainerOriginPosition = getComputedStyle(scrollContainer).position; if (scrollContainer !== document.body && scrollContainerOriginPosition === "static") { scrollContainer.style.position = "relative"; } handleStart(e); } } } }; const scrollListenerElement = scrollContainer === document.body ? document : scrollContainer; const onScroll = (e) => { const target = e.target; scrollInfo.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft }; if (isDraggingRef.current && scrollContainer) { const containerRect = scrollContainer.getBoundingClientRect(); const x = Math.min( moveClient.current.x - containerRect.left + scrollContainer.scrollLeft, scrollContainer.scrollWidth ); const y = Math.min( moveClient.current.y - containerRect.top + scrollContainer.scrollTop, scrollContainer.scrollHeight ); setMoveCoords({ x, y }); } }; const onMouseUp = () => { window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); window.removeEventListener("touchmove", onMouseMove); window.removeEventListener("touchend", onMouseUp); if (isDraggingRef.current) { scrollContainer.style.position = scrollContainerOriginPosition; cancelScroll(); setValue(selectingValue.current); handleEnd(); } reset(); }; const onMouseDown = (e) => { const isMouseEvent = e instanceof MouseEvent; if (isMouseEvent && e.button !== 0) { return; } if (isMouseEvent) { e.preventDefault(); } isMouseDowning = true; if (selectStartRangeRef.current !== "all") { setStartTarget(e.target); } const { clientX, clientY } = (0, import_utils.getClientXY)(e); const { left: left2, top: top2 } = scrollContainer.getBoundingClientRect(); setStartCoords({ x: clientX - left2 + scrollContainer.scrollLeft, y: clientY - top2 + scrollContainer.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); scrollListenerElement.addEventListener("scroll", onScroll); return () => { if (scrollContainerOriginPosition) { scrollContainer.style.position = scrollContainerOriginPosition; } cancelScroll(); dragContainer.removeEventListener("mousedown", onMouseDown); dragContainer.removeEventListener("touchstart", onMouseDown); scrollListenerElement.removeEventListener("scroll", onScroll); window.removeEventListener("mousemove", onMouseMove); window.removeEventListener("mouseup", onMouseUp); window.removeEventListener("touchmove", onMouseMove); window.removeEventListener("touchend", onMouseUp); }; }, [disabled, scrollContainer, dragContainer, isCanceled]); const contextValue = (0, import_react.useMemo)( () => ({ value, selectingValue, isDragging, boxPosition, mode, scrollContainer, startTarget, startInside, unmountItemsInfo, virtual, boxRef, compareFn }), [ value, isDragging, boxPosition, mode, scrollContainer, startTarget, unmountItemsInfo, virtual, boxRef, compareFn ] ); return /* @__PURE__ */ import_react.default.createElement(import_context.SelectableContext.Provider, { value: contextValue }, children, isDragging && scrollContainer && (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 } } ), scrollContainer )); } var Selectable_default = import_react.default.forwardRef(Selectable);