diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38cb1ab..7b6ccca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,7 +108,10 @@ jobs: cd /tmp/consumer node -e " const vm = require('node:vm'), fs = require('node:fs'); - const sandbox = { React: require('react'), _: require('lodash'), PropTypes: require('prop-types'), console }; + // React is the only global the bundle reads. The package has no + // runtime dependencies, so anything else here would mask a + // regression that reintroduced one. + const sandbox = { React: require('react'), console }; vm.createContext(sandbox); vm.runInContext(fs.readFileSync('node_modules/react-xarrows/lib/index.umd.js', 'utf8'), sandbox); const umd = sandbox.reactXarrow; diff --git a/package.json b/package.json index cfa0aed..9381373 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,6 @@ "url": "https://github.com/Eliav2/react-xarrows" }, "bugs": "https://github.com/Eliav2/react-xarrows/issues", - "dependencies": { - "@types/prop-types": "^15.7.3", - "lodash": "^4.17.21", - "prop-types": "^15.7.2" - }, "funding": { "type": "individual", "url": "https://www.paypal.com/donate?hosted_button_id=CRQ343F9VTRS8" @@ -64,7 +59,6 @@ "@microsoft/api-extractor": "^7.58.12", "@testing-library/jest-dom": "^7.0.0", "@testing-library/react": "^16.3.2", - "@types/lodash": "^4.14.168", "@types/node": "^26.1.2", "@types/react": "^18.3.31", "@types/react-dom": "^18.3.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea2ab31..40f9cc0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,16 +7,6 @@ settings: importers: .: - dependencies: - '@types/prop-types': - specifier: ^15.7.3 - version: 15.7.15 - lodash: - specifier: ^4.17.21 - version: 4.18.1 - prop-types: - specifier: ^15.7.2 - version: 15.8.1 devDependencies: '@arethetypeswrong/cli': specifier: ^0.18.5 @@ -33,9 +23,6 @@ importers: '@testing-library/react': specifier: ^16.3.2 version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.31))(@types/react@18.3.31)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/lodash': - specifier: ^4.14.168 - version: 4.17.25 '@types/node': specifier: ^26.1.2 version: 26.2.0 @@ -1255,9 +1242,6 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash@4.17.25': - resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} - '@types/node@26.2.0': resolution: {integrity: sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==} @@ -2261,9 +2245,6 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -4223,8 +4204,6 @@ snapshots: '@types/json-schema@7.0.15': {} - '@types/lodash@4.17.25': {} - '@types/node@26.2.0': dependencies: undici-types: 8.3.0 @@ -5294,8 +5273,6 @@ snapshots: lodash.merge@4.6.2: {} - lodash@4.18.1: {} - loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 diff --git a/src/Xarrow/Xarrow.tsx b/src/Xarrow/Xarrow.tsx index ae86c00..3184c56 100644 --- a/src/Xarrow/Xarrow.tsx +++ b/src/Xarrow/Xarrow.tsx @@ -2,7 +2,6 @@ import React, { useContext, useEffect, useLayoutEffect, useRef, useState } from import { xarrowPropsType } from '../types'; import useXarrowProps from './useXarrowProps'; import { XarrowContext } from '../Xwrapper'; -import XarrowPropTypes from './propTypes'; import { getPosition } from './utils/GetPosition'; import { getTotalLength } from './utils'; @@ -380,9 +379,4 @@ const Xarrow: React.FC = (props: xarrowPropsType) => { ); }; -////////////////////////////// -// propTypes - -Xarrow.propTypes = XarrowPropTypes; - export default Xarrow; diff --git a/src/Xarrow/propTypes.ts b/src/Xarrow/propTypes.ts deleted file mode 100644 index e95bd0c..0000000 --- a/src/Xarrow/propTypes.ts +++ /dev/null @@ -1,77 +0,0 @@ -import PT from 'prop-types'; -import { arrowShapes, cAnchorEdge, cPaths, cSvgElems } from '../constants'; - -const pAnchorPositionType = PT.oneOf(cAnchorEdge); - -const pAnchorCustomPositionType = PT.exact({ - position: pAnchorPositionType.isRequired, - offset: PT.exact({ - x: PT.number, - y: PT.number, - }).isRequired, -}); - -const _pAnchorType = PT.oneOfType([pAnchorPositionType, pAnchorCustomPositionType]); - -const pAnchorType = PT.oneOfType([_pAnchorType, PT.arrayOf(_pAnchorType)]); - -const pRefType = PT.oneOfType([PT.string, PT.exact({ current: PT.any })]); - -const _pLabelType = PT.oneOfType([PT.element, PT.string]); - -const pLabelsType = PT.exact({ - start: _pLabelType, - middle: _pLabelType, - end: _pLabelType, -}); - -const pSvgEdgeShapeType = PT.oneOf(Object.keys(arrowShapes) as Array); -// const pSvgElemType = PT.oneOf(cSvgElems); -const pSvgElemType = PT.any; -const pSvgEdgeType = PT.oneOfType([ - pSvgEdgeShapeType, - PT.exact({ - svgElem: pSvgElemType, - offsetForward: PT.number, - }).isRequired, -]); - -const XarrowPropTypes = { - start: pRefType.isRequired, - end: pRefType.isRequired, - startAnchor: pAnchorType, - endAnchor: pAnchorType, - labels: PT.oneOfType([_pLabelType, pLabelsType]), - color: PT.string, - lineColor: PT.string, - showHead: PT.bool, - headColor: PT.string, - headSize: PT.number, - tailSize: PT.number, - tailColor: PT.string, - strokeWidth: PT.number, - showTail: PT.bool, - path: PT.oneOf(cPaths), - showXarrow: PT.bool, - curveness: PT.number, - gridBreak: PT.string, - dashness: PT.oneOfType([PT.bool, PT.object]), - headShape: pSvgEdgeType, - tailShape: pSvgEdgeType, - animateDrawing: PT.oneOfType([PT.bool, PT.number]), - zIndex: PT.number, - passProps: PT.object, - arrowBodyProps: PT.object, - arrowHeadProps: PT.object, - arrowTailProps: PT.object, - SVGcanvasProps: PT.object, - divContainerProps: PT.object, - _extendSVGcanvas: PT.number, - _debug: PT.bool, - _cpx1Offset: PT.number, - _cpy1Offset: PT.number, - _cpx2Offset: PT.number, - _cpy2Offset: PT.number, -}; - -export default XarrowPropTypes; diff --git a/src/Xarrow/useXarrowProps.ts b/src/Xarrow/useXarrowProps.ts index 8612e2f..9aeddc3 100644 --- a/src/Xarrow/useXarrowProps.ts +++ b/src/Xarrow/useXarrowProps.ts @@ -10,7 +10,6 @@ import { xarrowPropsType, } from '../types'; import { getElementByPropGiven, getElemPos, xStr2absRelative } from './utils'; -import _ from 'lodash'; import { arrowShapes, cAnchorEdge, cArrowShapes } from '../constants'; import { anchorEdgeType, dimensionType } from '../privateTypes'; @@ -294,24 +293,29 @@ const initialValVars = { // const parseAllProps = () => parseGivenProps(defaultProps, initialParsedProps); -function deepCompareEquals(a, b) { - return _.isEqual(a, b); -} +// The only values ever compared here are element positions, which getElemPos +// always returns as four numbers, including on its null-element branch. A +// general deep-equality helper (this used to be lodash isEqual) is more than +// this needs, and would also have to be careful around the refs and React +// elements that appear elsewhere in the props. +const samePosition = (a: dimensionType | undefined, b: dimensionType | undefined) => { + if (a === b) return true; + if (!a || !b) return false; + return a.x === b.x && a.y === b.y && a.right === b.right && a.bottom === b.bottom; +}; -function useDeepCompareMemoize(value) { - const ref = useRef(); - // it can be done by using useMemo as well - // but useRef is rather cleaner and easier +function usePositionMemoize(value: dimensionType) { + const ref = useRef(); - if (!deepCompareEquals(value, ref.current)) { + if (!samePosition(value, ref.current)) { ref.current = value; } return ref.current; } -function useDeepCompareEffect(callback, dependencies) { - useLayoutEffect(callback, dependencies.map(useDeepCompareMemoize)); +function usePositionEffect(callback: () => void, dependencies: dimensionType[]) { + useLayoutEffect(callback, dependencies.map(usePositionMemoize)); } /** @@ -350,14 +354,14 @@ const useXarrowProps = ( // rerender whenever position of start element or end element changes const [valVars, setValVars] = useState(initialValVars); const startPos = getElemPos(propsRefs.start); - useDeepCompareEffect(() => { + usePositionEffect(() => { valVars.startPos = startPos; shouldUpdatePosition.current = true; setValVars({ ...valVars }); // console.log('start update pos', startPos); }, [startPos]); const endPos = getElemPos(propsRefs.end); - useDeepCompareEffect(() => { + usePositionEffect(() => { valVars.endPos = endPos; shouldUpdatePosition.current = true; setValVars({ ...valVars }); diff --git a/src/Xarrow/utils/GetPosition.tsx b/src/Xarrow/utils/GetPosition.tsx index f344641..318178e 100644 --- a/src/Xarrow/utils/GetPosition.tsx +++ b/src/Xarrow/utils/GetPosition.tsx @@ -2,7 +2,6 @@ import { useXarrowPropsResType } from '../useXarrowProps'; import React from 'react'; import { calcAnchors } from '../anchors'; import { getShortestLine, getSvgPos, getTotalLength } from './index'; -import _ from 'lodash'; import { cPaths } from '../../constants'; import { buzzierMinSols, bzFunction } from './buzzier'; @@ -48,8 +47,8 @@ export const getPosition = (xProps: useXarrowPropsResType, mainRef: React.Mutabl const startAnchorPosition = chosenStart.anchor.position; let endAnchorPosition = chosenEnd.anchor.position; - const startPoint = _.pick(chosenStart, ['x', 'y']), - endPoint = _.pick(chosenEnd, ['x', 'y']); + const startPoint = { x: chosenStart.x, y: chosenStart.y }, + endPoint = { x: chosenEnd.x, y: chosenEnd.y }; const mainDivPos = getSvgPos(svgRef); let cx0 = Math.min(startPoint.x, endPoint.x) - mainDivPos.x; diff --git a/vite.config.mts b/vite.config.mts index d33cadd..7a115f1 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -48,12 +48,11 @@ export default defineConfig({ ({ es: 'index.mjs', cjs: 'index.cjs', umd: 'index.umd.js' })[format] ?? `index.${format}.js`, }, rollupOptions: { - external: ['react', 'prop-types', 'lodash'], + // React is the only external left. The package has no runtime + // dependencies, so a script-tag consumer needs nothing but React. + external: ['react'], output: { - // lodash is read as `_`, which is the only global lodash actually - // defines. The webpack build asked for `root["lodash"]`, which lodash - // never sets, so the script-tag path was broken before this. - globals: { react: 'React', 'prop-types': 'PropTypes', lodash: '_' }, + globals: { react: 'React' }, // The entry mixes a default export with named ones. `named` keeps the // default reachable at `.default`, which is what the webpack UMD build // already did, so script-tag consumers see no change.