diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..b6a7d89c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/config/webpack.config.js b/config/webpack.config.js index b3851869..eba0ca92 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -20,6 +20,10 @@ module.exports = { resolve: { // our code can resolve 'xxx' instead of writing 'xxx.jsx' extensions: ['*', '.js', '.jsx'], + fallback: { + 'react/jsx-runtime': 'react/jsx-runtime.js', + 'react/jsx-dev-runtime': 'react/jsx-dev-runtime.js', + }, }, module: { // For every file that match regex in 'test', webpack pipes the code through to loaders diff --git a/package.json b/package.json index 3eba59b3..f3b7aaab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "script-custom-react-folder-tree", - "version": "5.0.11", + "version": "5.1.0", "description": "customizable react folder tree library", "main": "dist/react-folder-tree.bundle.js", "typings": "index.d.ts", @@ -43,7 +43,7 @@ "license": "MIT", "private": false, "scripts": { - "prebuild": "del \"dist/\"", + "prebuild": "rimraf dist", "build": "webpack --config config/webpack.prod.config.js", "start": "cross-env NODE_ENV=development node scripts/dev-server.js", "lint": "eslint src/ --ext .js,.jsx", @@ -99,10 +99,14 @@ }, "peerDependencies": { "react": "^16.8.0 || ^17", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-dom": "^16.8.0 || ^17" }, "dependencies": { "prop-types": "^15.7.2", + "react-dnd": "^16.0.1", + "react-dnd-html5-backend": "^16.0.1", "react-icons": "^4.1.0", "use-tree-state": "1.0.0" } diff --git a/src/components/FolderTree/FolderTree.jsx b/src/components/FolderTree/FolderTree.jsx index ceb986a4..c868da00 100644 --- a/src/components/FolderTree/FolderTree.jsx +++ b/src/components/FolderTree/FolderTree.jsx @@ -1,4 +1,6 @@ import React from 'react'; +import { DndProvider } from 'react-dnd'; +import { HTML5Backend } from 'react-dnd-html5-backend'; import PropTypes from 'prop-types'; import useTreeState, { testData, @@ -28,6 +30,10 @@ const FolderTree = ({ debug = false, searchData = null, showSearchData = false, + dndConfig = { + onDrop: null, + backend: null, + }, }) => { const options = { initCheckedStatus, @@ -56,6 +62,7 @@ const FolderTree = ({ debug, searchData, showSearchData, + dndConfig, }; /* ---------- @@ -69,9 +76,11 @@ const FolderTree = ({ return (
- - - + + + + +
); }; @@ -104,6 +113,10 @@ FolderTree.propTypes = { debug: PropTypes.bool, searchData: PropTypes.object, showSearchData: PropTypes.bool, + dndConfig: PropTypes.shape({ + backend: PropTypes.func, + onDrop: PropTypes.func, + }), }; export { diff --git a/src/components/SandBox/SandBox.jsx b/src/components/SandBox/SandBox.jsx index 4b8909e3..ea8da21c 100644 --- a/src/components/SandBox/SandBox.jsx +++ b/src/components/SandBox/SandBox.jsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; import FolderTree, { testData } from '../FolderTree/FolderTree'; const makeId = (length) => { @@ -63,7 +64,7 @@ const parseTestData = (node) => { }; /* eslint-disable */ -const SandBox = () => { +const SandBox = ({ dndConfig }) => { const onTreeStateChange = (state, e) => console.log({ state, e }); const [tree, setTree] = useState(null); @@ -79,6 +80,7 @@ const SandBox = () => { !!tree && (
{ @@ -110,4 +112,11 @@ const SandBox = () => { ); }; +SandBox.propTypes = { + dndConfig: PropTypes.shape({ + backend: PropTypes.func, + onDrop: PropTypes.func, + }), +}; + export default SandBox; diff --git a/src/components/TreeNode/TreeNode.jsx b/src/components/TreeNode/TreeNode.jsx index ba9e20f7..150fd047 100644 --- a/src/components/TreeNode/TreeNode.jsx +++ b/src/components/TreeNode/TreeNode.jsx @@ -2,6 +2,8 @@ import React, { useContext, useState, } from 'react'; +import { useDrag, useDrop } from 'react-dnd'; +import { NativeTypes } from 'react-dnd-html5-backend'; import PropTypes from 'prop-types'; import { AiFillCaretRight, @@ -25,6 +27,7 @@ import { iconClassName, getDefaultIcon, } from '../../utils/iconUtils'; +import { FileTreeDragTypes } from '../../utils/dnd'; const TreeNode = ({ path, @@ -51,6 +54,7 @@ const TreeNode = ({ onIconClick, showCheckbox, readOnly, + dndConfig, searchData, showSearchData, @@ -58,6 +62,18 @@ const TreeNode = ({ debug, } = useContext(ConfigContext); + const { onDrop } = dndConfig || {}; + + const [_, drop] = useDrop(() => ({ + accept: [FileTreeDragTypes.TREE_NODE, NativeTypes.FILE], + drop: item => { + onDrop?.(item); + }, + collect: monitor => ({ + isOver: monitor.isOver(), + }), + }), [onDrop]); + const isFolder = !!children; let matchCount = 0; @@ -80,6 +96,14 @@ const TreeNode = ({ ...restData, }; + const [__, drag] = useDrag({ + type: FileTreeDragTypes.TREE_NODE, + item: nodeData, + collect: monitor => ({ + isDragging: monitor.isDragging(), + }), + }); + const level = path.length; const offsetSize = !isFolder ? 0 : iconSize; @@ -245,6 +269,7 @@ const TreeNode = ({ return ( <>
{children}
; + +jest.mock('react-dnd', () => ({ + useDrag: jest.fn((...args) => [{ isDragging: false }, jest.fn()]), + useDrop: jest.fn((...args) => [{ isOver: false }, jest.fn()]), + DndProvider, +})); + +jest.mock('react-dnd-html5-backend', () => ({ + HTML5Backend: jest.fn(), +})); diff --git a/src/utils/dnd.js b/src/utils/dnd.js new file mode 100644 index 00000000..71f4d51b --- /dev/null +++ b/src/utils/dnd.js @@ -0,0 +1,7 @@ +import { HTML5Backend } from 'react-dnd-html5-backend'; + +export const AppDndBackend = HTML5Backend; + +export const FileTreeDragTypes = { + TREE_NODE: 'TREE_NODE', +}; diff --git a/yarn.lock b/yarn.lock index 5cd87231..9c0a8d4c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -910,6 +910,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.9.2": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/template@^7.14.5", "@babel/template@^7.3.3": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4" @@ -1152,6 +1159,21 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.15.tgz#6a9d143f7f4f49db2d782f9e1c8839a29b43ae23" integrity sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA== +"@react-dnd/asap@^5.0.1": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-5.0.2.tgz#1f81f124c1cd6f39511c11a881cfb0f715343488" + integrity sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A== + +"@react-dnd/invariant@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-4.0.2.tgz#b92edffca10a26466643349fac7cdfb8799769df" + integrity sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw== + +"@react-dnd/shallowequal@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4" + integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -2901,6 +2923,15 @@ discontinuous-range@1.0.0: resolved "https://registry.yarnpkg.com/discontinuous-range/-/discontinuous-range-1.0.0.tgz#e38331f0844bba49b9a9cb71c771585aab1bc65a" integrity sha1-44Mx8IRLukm5qctxx3FYWqsbxlo= +dnd-core@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-16.0.1.tgz#a1c213ed08961f6bd1959a28bb76f1a868360d19" + integrity sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng== + dependencies: + "@react-dnd/asap" "^5.0.1" + "@react-dnd/invariant" "^4.0.1" + redux "^4.2.0" + dns-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" @@ -3624,7 +3655,7 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== @@ -4140,6 +4171,13 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +hoist-non-react-statics@^3.3.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -6802,6 +6840,24 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +react-dnd-html5-backend@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz#87faef15845d512a23b3c08d29ecfd34871688b6" + integrity sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw== + dependencies: + dnd-core "^16.0.1" + +react-dnd@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-16.0.1.tgz#2442a3ec67892c60d40a1559eef45498ba26fa37" + integrity sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q== + dependencies: + "@react-dnd/invariant" "^4.0.1" + "@react-dnd/shallowequal" "^4.0.1" + dnd-core "^16.0.1" + fast-deep-equal "^3.1.3" + hoist-non-react-statics "^3.3.2" + "react-dom@^16.8.0 || ^17": version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -6821,7 +6877,7 @@ react-icons@^4.1.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-is@^16.13.1, react-is@^16.8.1, react-is@^16.8.6: +react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -6961,6 +7017,13 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +redux@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" + integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== + dependencies: + "@babel/runtime" "^7.9.2" + reflect.ownkeys@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460" @@ -6978,6 +7041,11 @@ regenerate@^1.4.0: resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== +regenerator-runtime@^0.13.11: + version "0.13.11" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" + integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== + regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"