{"id":8337,"date":"2024-03-04T15:24:10","date_gmt":"2024-03-04T14:24:10","guid":{"rendered":"https:\/\/solidt.eu\/site\/?p=8337"},"modified":"2024-03-04T15:28:52","modified_gmt":"2024-03-04T14:28:52","slug":"react-dragbox","status":"publish","type":"post","link":"https:\/\/solidt.eu\/site\/react-dragbox\/","title":{"rendered":"React DragBox"},"content":{"rendered":"\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"tsx\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">import React, { useRef, MouseEvent, ReactNode, useState, RefObject, useContext, useEffect } from \"react\";\r\nimport { keyStateProvider } from \"..\/..\/..\/helpers\/KeyStateProvider\";\r\nimport { Observable, Subject, Subscription } from \"rxjs\";\r\nimport { clearTextSelection } from \"..\/..\/..\/helpers\/tshelper\";\r\n\r\nexport interface DragArgs {\r\n    parentRect: DOMRect;\r\n    rect: DOMRect\r\n}\r\n\r\nexport const DragBoxContext = React.createContext&lt;Observable&lt;DragArgs> | undefined>(undefined);\r\n\r\nfunction checkIsIntersecting(parent: DOMRect, child: DOMRect) {\r\n    return parent.left &lt;= child.right &amp;&amp; parent.right >= child.left &amp;&amp; parent.top &lt;= child.bottom &amp;&amp; parent.bottom >= child.top;\r\n}\r\n\r\nexport function useDragSelect(elementRef: RefObject&lt;HTMLElement>, disabled?: boolean) {\r\n    const dragObservable = useContext(DragBoxContext);\r\n    const [isSelected, setIsSelected] = useState&lt;boolean>(false);\r\n    useEffect(() => {\r\n        if (disabled) return;\r\n        if (!dragObservable) return;\r\n        const subscription: Subscription = dragObservable.subscribe((args: DragArgs) => {\r\n            const e = elementRef.current;\r\n            if (!e) return;\r\n            if (keyStateProvider.keyStates.shift &amp;&amp; isSelected) return;\r\n            const bRect = e.getBoundingClientRect();\r\n            const pRect = args.parentRect;\r\n            const rect2 = new DOMRect(bRect.x - pRect.x, bRect.y - pRect.y, bRect.width, bRect.height);\r\n            const isIntersecting = checkIsIntersecting(args.rect, rect2);\r\n            setIsSelected(isIntersecting);\r\n        });\r\n        return () => subscription.unsubscribe();\r\n    }, [dragObservable, elementRef, isSelected, disabled]);\r\n    return isSelected &amp;&amp; !disabled;\r\n}\r\n\r\nexport function DragBox(props: { children: ReactNode }) {\r\n    const { children } = props;\r\n    const [subject] = useState(new Subject&lt;DragArgs>())\r\n    const containerRef = useRef&lt;HTMLDivElement>(null);\r\n    const draggingRef = useRef(false);\r\n    const startX = useRef(0);\r\n    const startY = useRef(0);\r\n\r\n    function getDragRect(e: MouseEvent) {\r\n        if (draggingRef.current &amp;&amp; containerRef.current) {\r\n            const rect = containerRef.current.getBoundingClientRect();\r\n            const width = Math.abs(e.clientX - startX.current);\r\n            const height = Math.abs(e.clientY - startY.current);\r\n            const left = Math.min(startX.current, e.clientX) - rect.left;\r\n            const top = Math.min(startY.current, e.clientY) - rect.top;\r\n            const dragRect = new DOMRect(left, top, width, height);\r\n            return dragRect;\r\n        }\r\n        return undefined;\r\n    }\r\n\r\n    const onMouseDown = (e: MouseEvent&lt;HTMLDivElement>) => {\r\n        if (keyStateProvider.keyStates.control) return;\r\n        e.preventDefault();\r\n        clearTextSelection();\r\n        draggingRef.current = true;\r\n        startX.current = e.clientX;\r\n        startY.current = e.clientY;\r\n        window.addEventListener(\"mousemove\", onMouseMove as any);\r\n        window.addEventListener(\"mouseup\", onMouseUp as any);\r\n    };\r\n\r\n    const onMouseMove = (e: MouseEvent) => {\r\n        const dragRect = getDragRect(e);\r\n        if (!dragRect) return;\r\n        drawDragBox(dragRect);\r\n    };\r\n\r\n    const onMouseUp = (e: MouseEvent) => {\r\n        const dragRect = getDragRect(e);\r\n        if (!dragRect) return;\r\n        subject.next({ parentRect: containerRef.current!.getBoundingClientRect(), rect: dragRect })\r\n\r\n        draggingRef.current = false;\r\n        window.removeEventListener(\"mousemove\", onMouseMove as any);\r\n        window.removeEventListener(\"mouseup\", onMouseUp as any);\r\n\r\n        const dragBox = containerRef.current?.querySelector(\".drag-box\") as HTMLElement;\r\n        if (!dragBox) return;\r\n        dragBox.style.display = \"none\";\r\n    };\r\n\r\n    const drawDragBox = (rect: DOMRect) => {\r\n        const dragBox = containerRef.current?.querySelector(\".drag-box\") as HTMLElement;\r\n        if (!dragBox) return;\r\n        dragBox.style.left = rect.x + \"px\";\r\n        dragBox.style.top = rect.y + \"px\";\r\n        dragBox.style.width = rect.width + \"px\";\r\n        dragBox.style.height = rect.height + \"px\";\r\n        dragBox.style.display = \"block\";\r\n    };\r\n\r\n    return (\r\n        &lt;div ref={containerRef} style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}>\r\n            &lt;div className=\"drag-box\" style={{ display: \"none\", position: \"absolute\", border: \"1.5px dotted red\", background: \"rgba(255,0,0,0.15)\", borderRadius: 3, zIndex: 999 }} \/>\r\n            &lt;div onMouseDown={onMouseDown} style={{ width: \"100%\", height: \"100%\" }}>\r\n                &lt;DragBoxContext.Provider value={subject}>\r\n                    {children}\r\n                &lt;\/DragBoxContext.Provider>\r\n            &lt;\/div>\r\n        &lt;\/div>\r\n    );\r\n};\r\n<\/pre><\/div>\n\n\n\n<div style=\"height: 250px; position:relative; margin-bottom: 50px;\" class=\"wp-block-simple-code-block-ace\"><pre class=\"wp-block-simple-code-block-ace\" style=\"position:absolute;top:0;right:0;bottom:0;left:0\" data-mode=\"tsx\" data-theme=\"monokai\" data-fontsize=\"14\" data-lines=\"Infinity\" data-showlines=\"true\" data-copy=\"false\">&lt;!-- Example -->\n&lt;DragBox>\n    &lt;SelectableItem \/>\n&lt;\/DragBox>\n\n\n\nexport function SelectableItem(props: { }) {\n    const elementRef = useRef&lt;HTMLDivElement>(null);\n    const isSelected = useDragSelect(elementRef, readOnly);\n\t\n    return (\n        &lt;div ref={elementRef} className={joinClasses(isSelected ? styles.selected : undefined)} onMouseDown={(e) => e.stopPropagation()}>\n            &lt;!-- content -->\n        &lt;\/div>\n    );\n}\n\nexport const joinClasses = (...args: any[]) => Array.from(args).filter(x => !!x).join(\" \");\n\n\nexport function clearTextSelection() {\n    const w = window as any;\n    const d = document as any;\n    if (w.getSelection) {\n        if (w.getSelection().empty) {  \/\/ Chrome\n            w.getSelection().empty();\n        } else if (w.getSelection().removeAllRanges) {  \/\/ Firefox\n            w.getSelection().removeAllRanges();\n        }\n    } else if (d.selection) {  \/\/ IE?\n        d.selection.empty();\n    }\n}\n<\/pre><\/div>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-8337","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/8337","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/comments?post=8337"}],"version-history":[{"count":2,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/8337\/revisions"}],"predecessor-version":[{"id":8340,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/posts\/8337\/revisions\/8340"}],"wp:attachment":[{"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/media?parent=8337"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/categories?post=8337"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/solidt.eu\/site\/wp-json\/wp\/v2\/tags?post=8337"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}