diff --git a/examples/vite-app/package.json b/examples/vite-app/package.json index 677f7289..2a05d338 100644 --- a/examples/vite-app/package.json +++ b/examples/vite-app/package.json @@ -13,6 +13,7 @@ "test-e2e": "start-server-and-test 'pnpm serve' http-get://localhost:3000 'pnpm test-e2e-cypress'" }, "dependencies": { + "@reactflow/node-resizer": "workspace:^1.0.0", "classcat": "^5.0.3", "dagre": "^0.8.5", "localforage": "^1.10.0", diff --git a/examples/vite-app/src/App/index.tsx b/examples/vite-app/src/App/index.tsx index 5b596f48..bb68c028 100644 --- a/examples/vite-app/src/App/index.tsx +++ b/examples/vite-app/src/App/index.tsx @@ -20,6 +20,7 @@ import Intersection from '../examples/Intersection'; import Layouting from '../examples/Layouting'; import MultiFlows from '../examples/MultiFlows'; import NestedNodes from '../examples/NestedNodes'; +import NodeResizer from '../examples/NodeResizer'; import NodeTypeChange from '../examples/NodeTypeChange'; import NodeTypesObjectChange from '../examples/NodeTypesObjectChange'; import Overview from '../examples/Overview'; @@ -174,6 +175,11 @@ const routes: IRoute[] = [ path: '/node-toolbar', component: NodeToolbar, }, + { + name: 'NodeResizer', + path: '/node-resizer', + component: NodeResizer, + }, { name: 'Overview', path: '/overview', diff --git a/examples/vite-app/src/examples/Basic/index.tsx b/examples/vite-app/src/examples/Basic/index.tsx index 92077b35..dfba56ef 100644 --- a/examples/vite-app/src/examples/Basic/index.tsx +++ b/examples/vite-app/src/examples/Basic/index.tsx @@ -94,7 +94,7 @@ const BasicFlow = () => { fitView defaultEdgeOptions={defaultEdgeOptions} selectNodesOnDrag={false} - nodeOrigin={nodeOrigin} + // nodeOrigin={nodeOrigin} > diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx new file mode 100644 index 00000000..996e24c5 --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/CustomResizer.tsx @@ -0,0 +1,27 @@ +import { memo, FC, CSSProperties } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; + +import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer'; +import '@reactflow/node-resizer/dist/style.css'; +import ResizeIcon from './ResizeIcon'; + +const controlStyle = { + background: 'transparent', + border: 'none', +}; + +const CustomNode: FC = ({ id, data }) => { + return ( + <> + + + + + +
{data.label}
+ + + ); +}; + +export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx b/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx new file mode 100644 index 00000000..27833556 --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/CustomResizer2.tsx @@ -0,0 +1,19 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; + +import { NodeResizer, NodeResizeControl } from '@reactflow/node-resizer'; +import '@reactflow/node-resizer/dist/style.css'; + +const CustomNode: FC = ({ id, data }) => { + return ( + <> + + + +
{data.label}
+ + + ); +}; + +export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx b/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx new file mode 100644 index 00000000..3a68cd9f --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/NodeResizerNode.tsx @@ -0,0 +1,18 @@ +import { memo, FC } from 'react'; +import { Handle, Position, NodeProps } from 'reactflow'; + +import { NodeResizer } from '@reactflow/node-resizer'; +import '@reactflow/node-resizer/dist/style.css'; + +const CustomNode: FC = ({ id, data, selected }) => { + return ( + <> + + +
{data.label}
+ + + ); +}; + +export default memo(CustomNode); diff --git a/examples/vite-app/src/examples/NodeResizer/ResizeIcon.tsx b/examples/vite-app/src/examples/NodeResizer/ResizeIcon.tsx new file mode 100644 index 00000000..8db1cb92 --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/ResizeIcon.tsx @@ -0,0 +1,24 @@ +function ResizeIcon() { + return ( + + + + + + + + ); +} + +export default ResizeIcon; diff --git a/examples/vite-app/src/examples/NodeResizer/index.tsx b/examples/vite-app/src/examples/NodeResizer/index.tsx new file mode 100644 index 00000000..683f9ab5 --- /dev/null +++ b/examples/vite-app/src/examples/NodeResizer/index.tsx @@ -0,0 +1,96 @@ +import { CSSProperties, useCallback, useState } from 'react'; +import ReactFlow, { Controls, addEdge, Position, Connection, useNodesState, useEdgesState, Panel } from 'reactflow'; + +import NodeResizerNode from './NodeResizerNode'; +import CustomResizer from './CustomResizer'; +import CustomResizer2 from './CustomResizer2'; + +const nodeTypes = { + resizer: NodeResizerNode, + customResizer: CustomResizer, + customResizer2: CustomResizer2, +}; + +const initialEdges = [ + { + id: 'e1-2', + source: '1', + target: '2', + }, +]; + +const initialNodes = [ + { + id: '1', + type: 'input', + data: { label: 'An input node' }, + position: { x: 0, y: 0 }, + sourcePosition: Position.Right, + }, + { + id: '2', + type: 'resizer', + data: { label: 'default resizer' }, + position: { x: 250, y: 0 }, + style: { + width: 200, + height: 150, + border: '1px solid #222', + fontSize: 10, + }, + }, + { + id: '3', + type: 'customResizer', + data: { label: 'resize control with child component' }, + position: { x: 250, y: 200 }, + style: { border: '1px solid #222', fontSize: 10, width: 100 }, + }, + { + id: '4', + type: 'customResizer2', + data: { label: 'resize controls' }, + position: { x: 100, y: 150 }, + style: { border: '1px solid #222', fontSize: 10 }, + }, + { + id: '5', + type: 'customResizer2', + data: { label: 'min width and height' }, + position: { x: 250, y: 250 }, + style: { border: '1px solid #222', fontSize: 10 }, + }, +]; + +const CustomNodeFlow = () => { + const [snapToGrid, setSnapToGrid] = useState(false); + const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes); + const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges); + + const onConnect = useCallback( + (connection: Connection) => setEdges((eds) => addEdge({ ...connection }, eds)), + [setEdges] + ); + + return ( + + + + + + + ); +}; + +export default CustomNodeFlow; diff --git a/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx b/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx index 4f8ab270..ca2ac0b7 100644 --- a/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx +++ b/examples/vite-app/src/examples/NodeToolbar/CustomNode.tsx @@ -4,7 +4,7 @@ import { Handle, Position, NodeProps, NodeToolbar } from 'reactflow'; const CustomNode: FC = ({ id, data }) => { return ( <> - + diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index 1713e819..bdbef9c7 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/background +## 11.0.7 + +### Patch Changes + +- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]: + - @reactflow/core@11.3.2 + ## 11.0.6 ### Patch Changes diff --git a/packages/background/package.json b/packages/background/package.json index 7df6af43..5011906c 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/background", - "version": "11.0.6", + "version": "11.0.7", "description": "Background component with different variants for React Flow", "keywords": [ "react", diff --git a/packages/controls/CHANGELOG.md b/packages/controls/CHANGELOG.md index 40de22b6..9df35ad9 100644 --- a/packages/controls/CHANGELOG.md +++ b/packages/controls/CHANGELOG.md @@ -1,5 +1,12 @@ # @reactflow/controls +## 11.0.7 + +### Patch Changes + +- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]: + - @reactflow/core@11.3.2 + ## 11.0.6 ### Patch Changes diff --git a/packages/controls/package.json b/packages/controls/package.json index 91d19ade..7458ee83 100644 --- a/packages/controls/package.json +++ b/packages/controls/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/controls", - "version": "11.0.6", + "version": "11.0.7", "description": "Component to control the viewport of a React Flow instance", "keywords": [ "react", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 59a8cfca..b0b48f29 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,17 @@ # @reactflow/core +## 11.3.2 + +In this update we did some changes so that we could implement the new [``](https://reactflow.dev/docs/api/nodes/node-resizer/) component more smoothly. + +### Patch Changes + +- [#2646](https://github.com/wbkd/react-flow/pull/2646) [`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a) - Fix getRectOfNodes +- [#2648](https://github.com/wbkd/react-flow/pull/2648) [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae) - Allow middle mouse pan over edges +- [#2647](https://github.com/wbkd/react-flow/pull/2647) [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d) Thanks [@neo](https://github.com/neo)! - Invalidate node trying to connect itself with the same handle +- [#2626](https://github.com/wbkd/react-flow/pull/2626) [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18) - Export the useNodeId hook, refactor how changes are applied and create a helper function +- [#2642](https://github.com/wbkd/react-flow/pull/2642) [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6) - Ignore key events for nodes when input is focused + ## 11.3.1 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 58f31d51..9bd0f67b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/core", - "version": "11.3.1", + "version": "11.3.2", "description": "Core components and util functions of React Flow.", "keywords": [ "react", diff --git a/packages/core/src/components/Handle/handler.ts b/packages/core/src/components/Handle/handler.ts index ff726d30..0052120f 100644 --- a/packages/core/src/components/Handle/handler.ts +++ b/packages/core/src/components/Handle/handler.ts @@ -60,7 +60,7 @@ export function checkElementBelowIsValid( const isValid = connectionMode === ConnectionMode.Strict ? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget) - : true; + : elementBelowNodeId !== nodeId || elementBelowHandleId !== handleId; if (isValid) { result.isValid = isValidConnection(connection); diff --git a/packages/core/src/components/Handle/index.tsx b/packages/core/src/components/Handle/index.tsx index c47035a9..a2d0502f 100644 --- a/packages/core/src/components/Handle/index.tsx +++ b/packages/core/src/components/Handle/index.tsx @@ -1,9 +1,9 @@ -import { memo, useContext, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent } from 'react'; +import { memo, HTMLAttributes, forwardRef, MouseEvent as ReactMouseEvent } from 'react'; import cc from 'classcat'; import shallow from 'zustand/shallow'; import { useStore, useStoreApi } from '../../hooks/useStore'; -import NodeIdContext from '../../contexts/NodeIdContext'; +import { useNodeId } from '../../contexts/NodeIdContext'; import { checkElementBelowIsValid, handleMouseDown } from './handler'; import { getHostForElement } from '../../utils'; import { addEdge } from '../../utils/graph'; @@ -37,7 +37,9 @@ const Handle = forwardRef( ref ) => { const store = useStoreApi(); - const nodeId = useContext(NodeIdContext) as string; + + // @fixme: remove type assertion and handle nodeId === null + const nodeId = useNodeId() as string; const { connectionStartHandle, connectOnClick, noPanClassName } = useStore(selector, shallow); const handleId = id || null; diff --git a/packages/core/src/components/Nodes/wrapNode.tsx b/packages/core/src/components/Nodes/wrapNode.tsx index afb1f761..70b36ddb 100644 --- a/packages/core/src/components/Nodes/wrapNode.tsx +++ b/packages/core/src/components/Nodes/wrapNode.tsx @@ -8,7 +8,7 @@ import { ARIA_NODE_DESC_KEY } from '../A11yDescriptions'; import useDrag from '../../hooks/useDrag'; import useUpdateNodePositions from '../../hooks/useUpdateNodePositions'; import { getMouseHandler, handleNodeClick } from './utils'; -import { elementSelectionKeys } from '../../utils'; +import { elementSelectionKeys, isInputDOMNode } from '../../utils'; import type { NodeProps, WrapNodeProps, XYPosition } from '../../types'; export const arrowKeyDiffs: Record = { @@ -84,7 +84,12 @@ export default (NodeComponent: ComponentType) => { }; const onKeyDown = (event: KeyboardEvent) => { + if (isInputDOMNode(event)) { + return; + } + const { snapGrid, snapToGrid } = store.getState(); + if (elementSelectionKeys.includes(event.key) && isSelectable) { const unselect = event.key === 'Escape'; if (unselect) { diff --git a/packages/core/src/container/NodeRenderer/index.tsx b/packages/core/src/container/NodeRenderer/index.tsx index 70a62dfe..389cd2d0 100644 --- a/packages/core/src/container/NodeRenderer/index.tsx +++ b/packages/core/src/container/NodeRenderer/index.tsx @@ -57,6 +57,7 @@ const NodeRenderer = (props: NodeRendererProps) => { nodeElement: entry.target as HTMLDivElement, forceUpdate: true, })); + updateNodeDimensions(updates); }); diff --git a/packages/core/src/container/ZoomPane/index.tsx b/packages/core/src/container/ZoomPane/index.tsx index a5783a7a..f94bbe58 100644 --- a/packages/core/src/container/ZoomPane/index.tsx +++ b/packages/core/src/container/ZoomPane/index.tsx @@ -228,7 +228,11 @@ const ZoomPane = ({ const zoomScroll = zoomActivationKeyPressed || zoomOnScroll; const pinchZoom = zoomOnPinch && event.ctrlKey; - if (event.button === 1 && event.type === 'mousedown' && event.target.closest(`.react-flow__node`)) { + if ( + event.button === 1 && + event.type === 'mousedown' && + (isWrappedWithClass(event, 'react-flow__node') || isWrappedWithClass(event, 'react-flow__edge')) + ) { return true; } diff --git a/packages/core/src/contexts/NodeIdContext.ts b/packages/core/src/contexts/NodeIdContext.ts index c43a1c3b..28a14b78 100644 --- a/packages/core/src/contexts/NodeIdContext.ts +++ b/packages/core/src/contexts/NodeIdContext.ts @@ -1,7 +1,12 @@ -import { createContext } from 'react'; +import { createContext, useContext } from 'react'; export const NodeIdContext = createContext(null); export const Provider = NodeIdContext.Provider; export const Consumer = NodeIdContext.Consumer; +export const useNodeId = (): string | null => { + const nodeId = useContext(NodeIdContext); + return nodeId; +}; + export default NodeIdContext; diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 47c2113a..3ab1d0bb 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -1,15 +1,14 @@ -import { useEffect, useRef, useState, useCallback } from 'react'; +import { useEffect, useRef, useState } from 'react'; import type { RefObject, MouseEvent } from 'react'; import { drag } from 'd3-drag'; import { select } from 'd3-selection'; -import type { D3DragEvent, SubjectPosition } from 'd3'; import { useStoreApi } from '../../hooks/useStore'; import { getDragItems, getEventHandlerParams, hasSelector, calcNextPosition } from './utils'; import { handleNodeClick } from '../../components/Nodes/utils'; -import type { NodeDragItem, Node, SelectionDragHandler } from '../../types'; +import useGetPointerPosition from '../useGetPointerPosition'; +import type { NodeDragItem, Node, SelectionDragHandler, UseDragEvent } from '../../types'; -export type UseDragEvent = D3DragEvent; export type UseDragData = { dx: number; dy: number }; type UseDragParams = { @@ -40,24 +39,7 @@ function useDrag({ const dragItems = useRef(); const lastPos = useRef<{ x: number | null; y: number | null }>({ x: null, y: null }); - // returns the pointer position projected to the RF coordinate system - const getPointerPosition = useCallback(({ sourceEvent }: UseDragEvent) => { - const { transform, snapGrid, snapToGrid } = store.getState(); - const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX; - const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY; - - const pointerPos = { - x: (x - transform[0]) / transform[2], - y: (y - transform[1]) / transform[2], - }; - - // we need the snapped position in order to be able to skip unnecessary drag events - return { - xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x, - ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y, - ...pointerPos, - }; - }, []); + const getPointerPosition = useGetPointerPosition(); useEffect(() => { if (nodeRef?.current) { diff --git a/packages/core/src/hooks/useDrag/utils.ts b/packages/core/src/hooks/useDrag/utils.ts index 61b0d3f9..74484baf 100644 --- a/packages/core/src/hooks/useDrag/utils.ts +++ b/packages/core/src/hooks/useDrag/utils.ts @@ -1,6 +1,6 @@ import type { RefObject } from 'react'; -import { clampPosition, devWarn } from '../../utils'; +import { clampPosition, devWarn, isNumeric } from '../../utils'; import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types'; import { getNodePositionWithOrigin } from '../../utils/graph'; @@ -69,17 +69,14 @@ export function calcNextPosition( if (node.extent === 'parent') { if (node.parentNode && node.width && node.height) { const parent = nodeInternals.get(node.parentNode); - const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin); + const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, nodeOrigin).positionAbsolute; currentExtent = - parentPosition.positionAbsolute && parent?.width && parent?.height + parent && isNumeric(parentX) && isNumeric(parentY) && isNumeric(parent.width) && isNumeric(parent.height) ? [ + [parentX + node.width * nodeOrigin[0], parentY + node.height * nodeOrigin[1]], [ - parentPosition.positionAbsolute.x + node.width * nodeOrigin[0], - parentPosition.positionAbsolute.y + node.height * nodeOrigin[1], - ], - [ - parentPosition.positionAbsolute.x + parent.width - node.width + node.width * nodeOrigin[0], - parentPosition.positionAbsolute.y + parent.height - node.height + node.height * nodeOrigin[1], + parentX + parent.width - node.width + node.width * nodeOrigin[0], + parentY + parent.height - node.height + node.height * nodeOrigin[1], ], ] : currentExtent; @@ -90,8 +87,7 @@ export function calcNextPosition( } } else if (node.extent && node.parentNode) { const parent = nodeInternals.get(node.parentNode); - const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin); - const { x: parentX, y: parentY } = parentPosition.positionAbsolute; + const { x: parentX, y: parentY } = getNodePositionWithOrigin(parent, nodeOrigin).positionAbsolute; currentExtent = [ [node.extent[0][0] + parentX, node.extent[0][1] + parentY], [node.extent[1][0] + parentX, node.extent[1][1] + parentY], diff --git a/packages/core/src/hooks/useGetPointerPosition.ts b/packages/core/src/hooks/useGetPointerPosition.ts new file mode 100644 index 00000000..38c12470 --- /dev/null +++ b/packages/core/src/hooks/useGetPointerPosition.ts @@ -0,0 +1,31 @@ +import { useCallback } from 'react'; + +import { useStoreApi } from './useStore'; +import type { UseDragEvent } from '../types'; + +function useGetPointerPosition() { + const store = useStoreApi(); + + // returns the pointer position projected to the RF coordinate system + const getPointerPosition = useCallback(({ sourceEvent }: UseDragEvent) => { + const { transform, snapGrid, snapToGrid } = store.getState(); + const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX; + const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY; + + const pointerPos = { + x: (x - transform[0]) / transform[2], + y: (y - transform[1]) / transform[2], + }; + + // we need the snapped position in order to be able to skip unnecessary drag events + return { + xSnapped: snapToGrid ? snapGrid[0] * Math.round(pointerPos.x / snapGrid[0]) : pointerPos.x, + ySnapped: snapToGrid ? snapGrid[1] * Math.round(pointerPos.y / snapGrid[1]) : pointerPos.y, + ...pointerPos, + }; + }, []); + + return getPointerPosition; +} + +export default useGetPointerPosition; diff --git a/packages/core/src/hooks/useKeyPress.ts b/packages/core/src/hooks/useKeyPress.ts index 4273feaa..20dcf37e 100644 --- a/packages/core/src/hooks/useKeyPress.ts +++ b/packages/core/src/hooks/useKeyPress.ts @@ -1,5 +1,6 @@ import { useState, useEffect, useRef, useMemo } from 'react'; +import { isInputDOMNode } from '../utils'; import type { KeyCode } from '../types'; type Keys = Array; @@ -106,14 +107,3 @@ function isMatchingKey(keyCodes: Array, pressedKeys: PressedKeys, isUp: bo function useKeyOrCode(eventCode: string, keysToWatch: KeyCode): KeyOrCode { return keysToWatch.includes(eventCode) ? 'code' : 'key'; } - -function isInputDOMNode(event: KeyboardEvent): boolean { - // using composed path for handling shadow dom - const target = (event.composedPath?.()[0] || event.target) as HTMLElement; - - return ( - ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) || - target?.hasAttribute('contenteditable') || - !!target?.closest('.nokey') - ); -} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d51540e9..c4a5b19e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -38,5 +38,7 @@ export { useStore, useStoreApi } from './hooks/useStore'; export { default as useOnViewportChange } from './hooks/useOnViewportChange'; export { default as useOnSelectionChange } from './hooks/useOnSelectionChange'; export { default as useNodesInitialized } from './hooks/useNodesInitialized'; +export { default as useGetPointerPosition } from './hooks/useGetPointerPosition'; +export { useNodeId } from './contexts/NodeIdContext'; export * from './types'; diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index 5ffd64c3..bdb02137 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -17,6 +17,7 @@ import type { NodePositionChange, NodeDragItem, UnselectNodesAndEdgesParams, + NodeChange, } from '../types'; const createRFStore = () => @@ -103,35 +104,40 @@ const createRFStore = () => } }, updateNodePositions: (nodeDragItems: NodeDragItem[] | Node[], positionChanged = true, dragging = false) => { + const { triggerNodeChanges } = get(); + + const changes = nodeDragItems.map((node) => { + const change: NodePositionChange = { + id: node.id, + type: 'position', + dragging, + }; + + if (positionChanged) { + change.positionAbsolute = node.positionAbsolute; + change.position = node.position; + } + + return change; + }); + + triggerNodeChanges(changes); + }, + + triggerNodeChanges: (changes: NodeChange[]) => { const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin } = get(); - if (hasDefaultNodes || onNodesChange) { - const changes = nodeDragItems.map((node) => { - const change: NodePositionChange = { - id: node.id, - type: 'position', - dragging, - }; - - if (positionChanged) { - change.positionAbsolute = node.positionAbsolute; - change.position = node.position; - } - - return change; - }); - - if (changes?.length) { - if (hasDefaultNodes) { - const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values())); - const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin); - set({ nodeInternals: nextNodeInternals }); - } - - onNodesChange?.(changes); + if (changes?.length) { + if (hasDefaultNodes) { + const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values())); + const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin); + set({ nodeInternals: nextNodeInternals }); } + + onNodesChange?.(changes); } }, + addSelectedNodes: (selectedNodeIds: string[]) => { const { multiSelectionActive, nodeInternals, edges } = get(); let changedNodes: NodeSelectionChange[]; diff --git a/packages/core/src/styles/style.css b/packages/core/src/styles/style.css index 54b37f49..9850564d 100644 --- a/packages/core/src/styles/style.css +++ b/packages/core/src/styles/style.css @@ -13,6 +13,13 @@ } } +.react-flow__node.selectable { + &:focus, + &:focus-visible { + outline: none; + } +} + .react-flow__node-default, .react-flow__node-input, .react-flow__node-output, @@ -37,7 +44,6 @@ &:focus, &:focus-visible { box-shadow: 0 0 0 0.5px #1a192b; - outline: none; } } } diff --git a/packages/core/src/types/changes.ts b/packages/core/src/types/changes.ts index b4395803..375c8bf2 100644 --- a/packages/core/src/types/changes.ts +++ b/packages/core/src/types/changes.ts @@ -7,7 +7,9 @@ import type { Edge } from './edges'; export type NodeDimensionChange = { id: string; type: 'dimensions'; - dimensions: Dimensions; + dimensions?: Dimensions; + updateStyle?: boolean; + resizing?: boolean; }; export type NodePositionChange = { diff --git a/packages/core/src/types/general.ts b/packages/core/src/types/general.ts index b18f4d32..472df101 100644 --- a/packages/core/src/types/general.ts +++ b/packages/core/src/types/general.ts @@ -1,9 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import type { MouseEvent as ReactMouseEvent, ComponentType, MemoExoticComponent } from 'react'; -import type { Selection as D3Selection, ZoomBehavior } from 'd3'; +import type { D3DragEvent, Selection as D3Selection, SubjectPosition, ZoomBehavior } from 'd3'; import type { XYPosition, Rect, Transform, CoordinateExtent } from './utils'; -import type { NodeChange, EdgeChange } from './changes'; +import type { NodeChange, EdgeChange, NodePositionChange } from './changes'; import type { Node, NodeInternals, @@ -226,6 +226,7 @@ export type ReactFlowActions = { setNodeExtent: (nodeExtent: CoordinateExtent) => void; cancelConnection: () => void; reset: () => void; + triggerNodeChanges: (changes: NodeChange[]) => void; }; export type ReactFlowState = ReactFlowStore & ReactFlowActions; @@ -245,3 +246,5 @@ export type ProOptions = { account?: string; hideAttribution: boolean; }; + +export type UseDragEvent = D3DragEvent; diff --git a/packages/core/src/types/nodes.ts b/packages/core/src/types/nodes.ts index 39d8755c..c27e79cf 100644 --- a/packages/core/src/types/nodes.ts +++ b/packages/core/src/types/nodes.ts @@ -31,6 +31,7 @@ export type Node = { positionAbsolute?: XYPosition; ariaLabel?: string; focusable?: boolean; + resizing?: boolean; // only used internally [internalsSymbol]?: { diff --git a/packages/core/src/utils/changes.ts b/packages/core/src/utils/changes.ts index 897c1ffb..5deb3fdc 100644 --- a/packages/core/src/utils/changes.ts +++ b/packages/core/src/utils/changes.ts @@ -50,58 +50,67 @@ function applyChanges(changes: any[], elements: any[]): any[] { const initElements: any[] = changes.filter((c) => c.type === 'add').map((c) => c.item); return elements.reduce((res: any[], item: any) => { - const currentChange = changes.find((c) => c.id === item.id); + const currentChanges = changes.filter((c) => c.id === item.id); - if (currentChange) { - switch (currentChange.type) { - case 'select': { - res.push({ ...item, selected: currentChange.selected }); - return res; - } - case 'position': { - const updateItem = { ...item }; + if (currentChanges.length === 0) { + res.push(item); + return res; + } - if (typeof currentChange.position !== 'undefined') { - updateItem.position = currentChange.position; + const updateItem = { ...item }; + + for (const currentChange of currentChanges) { + if (currentChange) { + switch (currentChange.type) { + case 'select': { + updateItem.selected = currentChange.selected; + break; } + case 'position': { + if (typeof currentChange.position !== 'undefined') { + updateItem.position = currentChange.position; + } - if (typeof currentChange.positionAbsolute !== 'undefined') { - updateItem.positionAbsolute = currentChange.positionAbsolute; + if (typeof currentChange.positionAbsolute !== 'undefined') { + updateItem.positionAbsolute = currentChange.positionAbsolute; + } + + if (typeof currentChange.dragging !== 'undefined') { + updateItem.dragging = currentChange.dragging; + } + + if (updateItem.expandParent) { + handleParentExpand(res, updateItem); + } + break; } + case 'dimensions': { + if (typeof currentChange.dimensions !== 'undefined') { + updateItem.width = currentChange.dimensions.width; + updateItem.height = currentChange.dimensions.height; + } - if (typeof currentChange.dragging !== 'undefined') { - updateItem.dragging = currentChange.dragging; + if (typeof currentChange.updateStyle !== 'undefined') { + updateItem.style = { ...(updateItem.style || {}), ...currentChange.dimensions }; + } + + if (typeof currentChange.resizing === 'boolean') { + updateItem.resizing = currentChange.resizing; + } + + if (updateItem.expandParent) { + handleParentExpand(res, updateItem); + } + break; } - - if (updateItem.expandParent) { - handleParentExpand(res, updateItem); + case 'remove': { + return res; } - - res.push(updateItem); - return res; - } - case 'dimensions': { - const updateItem = { ...item }; - - if (typeof currentChange.dimensions !== 'undefined') { - updateItem.width = currentChange.dimensions.width; - updateItem.height = currentChange.dimensions.height; - } - - if (updateItem.expandParent) { - handleParentExpand(res, updateItem); - } - - res.push(updateItem); - return res; - } - case 'remove': { - return res; } } } - res.push(item); + res.push(updateItem); return res; }, initElements); } diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index ea390967..9a3d4c42 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -2,7 +2,7 @@ import type { Selection as D3Selection } from 'd3'; import { boxToRect, clamp, devWarn, getBoundsOfBoxes, getOverlappingArea, rectToBox } from '../utils'; -import type { +import { Node, Edge, Connection, @@ -156,18 +156,22 @@ export const getNodePositionWithOrigin = ( }; } - const offset: XYPosition = { - x: (node.width ?? 0) * nodeOrigin[0], - y: (node.height ?? 0) * nodeOrigin[1], + const offsetX = (node.width ?? 0) * nodeOrigin[0]; + const offsetY = (node.height ?? 0) * nodeOrigin[1]; + + const position: XYPosition = { + x: node.position.x - offsetX, + y: node.position.y - offsetY, }; return { - x: node.position.x - offset.x, - y: node.position.y - offset.y, - positionAbsolute: { - x: (node.positionAbsolute?.x ?? 0) - offset.x, - y: (node.positionAbsolute?.y ?? 0) - offset.y, - }, + ...position, + positionAbsolute: node.positionAbsolute + ? { + x: node.positionAbsolute.x - offsetX, + y: node.positionAbsolute.y - offsetY, + } + : position, }; }; @@ -178,15 +182,12 @@ export const getRectOfNodes = (nodes: Node[], nodeOrigin: NodeOrigin = [0, 0]): const box = nodes.reduce( (currBox, node) => { - const { positionAbsolute, ...position } = getNodePositionWithOrigin(node, nodeOrigin); - const nodeX = positionAbsolute ? positionAbsolute.x : position.x; - const nodeY = positionAbsolute ? positionAbsolute.y : position.y; - + const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; return getBoundsOfBoxes( currBox, rectToBox({ - x: nodeX, - y: nodeY, + x, + y, width: node.width || 0, height: node.height || 0, }) diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index d869a81a..f6e9d994 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,3 +1,4 @@ +import type { KeyboardEvent as ReactKeyboardEvent } from 'react'; import type { Dimensions, Node, XYPosition, CoordinateExtent, Box, Rect } from '../types'; export const getDimensions = (node: HTMLDivElement): Dimensions => ({ @@ -69,3 +70,18 @@ export const devWarn = (message: string) => { console.warn(`[React Flow]: ${message}`); } }; + +const isReactKeyboardEvent = (event: KeyboardEvent | ReactKeyboardEvent): event is ReactKeyboardEvent => + 'nativeEvent' in event; + +export function isInputDOMNode(event: KeyboardEvent | ReactKeyboardEvent): boolean { + const kbEvent = isReactKeyboardEvent(event) ? event.nativeEvent : event; + // using composed path for handling shadow dom + const target = (kbEvent.composedPath?.()?.[0] || event.target) as HTMLElement; + + return ( + ['INPUT', 'SELECT', 'TEXTAREA'].includes(target?.nodeName) || + target?.hasAttribute('contenteditable') || + !!target?.closest('.nokey') + ); +} diff --git a/packages/minimap/CHANGELOG.md b/packages/minimap/CHANGELOG.md index b9cbb0be..a98b4072 100644 --- a/packages/minimap/CHANGELOG.md +++ b/packages/minimap/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactflow/minimap +## 11.2.3 + +### Patch Changes + +- [#2646](https://github.com/wbkd/react-flow/pull/2646) [`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a) - Cleanup get node position with origin usage + +- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]: + - @reactflow/core@11.3.2 + ## 11.2.2 ### Patch Changes diff --git a/packages/minimap/package.json b/packages/minimap/package.json index d6dc1331..ce4db650 100644 --- a/packages/minimap/package.json +++ b/packages/minimap/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/minimap", - "version": "11.2.2", + "version": "11.2.3", "description": "Minimap component for React Flow.", "keywords": [ "react", diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 722f1423..b1d4c555 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -167,13 +167,13 @@ function MiniMap({ > {ariaLabel && {ariaLabel}} {nodes.map((node) => { - const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin); + const { x, y } = getNodePositionWithOrigin(node, nodeOrigin).positionAbsolute; return ( `](https://reactflow.dev/docs/api/nodes/node-resizer/) component and a [``](https://reactflow.dev/docs/api/nodes/node-resizer/#noderesizecontrol--component) component. + +### Major Changes + +- [#2626](https://github.com/wbkd/react-flow/pull/2626) [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18) - Add a node resizer component that can be used to resize a custom node + +### Patch Changes + +- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]: + - @reactflow/core@11.3.2 diff --git a/packages/node-resizer/README.md b/packages/node-resizer/README.md new file mode 100644 index 00000000..d808473e --- /dev/null +++ b/packages/node-resizer/README.md @@ -0,0 +1,10 @@ +# @reactflow/node-resizer + +A resizer component for React Flow that can be attached to a node. + +## Installation + +```sh +npm install @reactflow/node-resizer +``` + diff --git a/packages/node-resizer/package.json b/packages/node-resizer/package.json new file mode 100644 index 00000000..43b00744 --- /dev/null +++ b/packages/node-resizer/package.json @@ -0,0 +1,71 @@ +{ + "name": "@reactflow/node-resizer", + "version": "1.0.0", + "description": "A helper component for resizing nodes.", + "keywords": [ + "react", + "node-based UI", + "graph", + "diagram", + "workflow", + "react-flow" + ], + "files": [ + "dist" + ], + "source": "src/index.tsx", + "main": "dist/umd/index.js", + "module": "dist/esm/index.js", + "types": "dist/esm/index.d.ts", + "sideEffects": [ + "*.css" + ], + "publishConfig": { + "access": "public" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/wbkd/react-flow.git", + "directory": "packages/node-resizer" + }, + "scripts": { + "dev": "concurrently \"rollup --config node:@reactflow/rollup-config --watch\" pnpm:css-watch", + "build": "rollup --config node:@reactflow/rollup-config --environment NODE_ENV:production && npm run css", + "css": "postcss src/*.css --config ../../tooling/postcss-config/postcss.config.js --dir dist", + "css-watch": "pnpm css --watch", + "lint": "eslint --ext .js,.jsx,.ts,.tsx src", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@reactflow/core": "workspace:*", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.1.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + }, + "devDependencies": { + "@reactflow/eslint-config": "workspace:*", + "@reactflow/rollup-config": "workspace:*", + "@reactflow/tsconfig": "workspace:*", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/node": "^18.7.16", + "@types/react": "^18.0.19", + "@types/react-dom": "^18.0.6", + "react": "^18.2.0", + "typescript": "^4.8.3" + }, + "rollup": { + "globals": { + "zustand": "Zustand", + "zustand/shallow": "zustandShallow", + "classcat": "cc" + }, + "name": "ReactFlowNodeResizer" + } +} diff --git a/packages/node-resizer/src/NodeResizer.tsx b/packages/node-resizer/src/NodeResizer.tsx new file mode 100644 index 00000000..1b2aee1f --- /dev/null +++ b/packages/node-resizer/src/NodeResizer.tsx @@ -0,0 +1,45 @@ +import ResizeControl from './ResizeControl'; +import { ControlPosition, NodeResizerProps, ResizeControlVariant, ControlLinePosition } from './types'; + +const handleControls: ControlPosition[] = ['top-left', 'top-right', 'bottom-left', 'bottom-right']; +const lineControls: ControlLinePosition[] = ['top', 'right', 'bottom', 'left']; + +export default function NodeResizer({ + nodeId, + isVisible = true, + handleClassName, + handleStyle, + lineClassName, + lineStyle, + color, +}: NodeResizerProps) { + if (!isVisible) { + return null; + } + + return ( + <> + {lineControls.map((c) => ( + + ))} + {handleControls.map((c) => ( + + ))} + + ); +} diff --git a/packages/node-resizer/src/ResizeControl.tsx b/packages/node-resizer/src/ResizeControl.tsx new file mode 100644 index 00000000..47050fec --- /dev/null +++ b/packages/node-resizer/src/ResizeControl.tsx @@ -0,0 +1,178 @@ +import { useRef, useEffect, memo } from 'react'; +import cc from 'classcat'; +import { drag } from 'd3-drag'; +import { select } from 'd3-selection'; +import { + useStoreApi, + useGetPointerPosition, + NodeChange, + NodeDimensionChange, + useNodeId, + NodePositionChange, +} from '@reactflow/core'; + +import { ResizeDragEvent, ResizeControlProps, ResizeControlLineProps, ResizeControlVariant } from './types'; + +const initPrevValues = { width: 0, height: 0, x: 0, y: 0 }; + +const initStartValues = { + ...initPrevValues, + pointerX: 0, + pointerY: 0, +}; + +function ResizeControl({ + nodeId, + position, + variant = ResizeControlVariant.Handle, + className, + style = {}, + children, + color, + minWidth = 10, + minHeight = 10, +}: ResizeControlProps) { + const contextNodeId = useNodeId(); + const id = typeof nodeId === 'string' ? nodeId : contextNodeId; + const store = useStoreApi(); + const resizeControlRef = useRef(null); + const startValues = useRef(initStartValues); + const prevValues = useRef(initPrevValues); + const getPointerPosition = useGetPointerPosition(); + const defaultPosition = variant === ResizeControlVariant.Line ? 'right' : 'bottom-right'; + const controlPosition = position ?? defaultPosition; + + useEffect(() => { + if (!resizeControlRef.current || !id) { + return; + } + + const selection = select(resizeControlRef.current); + const dragHandler = drag() + .on('start', (event: ResizeDragEvent) => { + const node = store.getState().nodeInternals.get(id); + const { xSnapped, ySnapped } = getPointerPosition(event); + + prevValues.current = { + width: node?.width ?? 0, + height: node?.height ?? 0, + x: node?.position.x ?? 0, + y: node?.position.y ?? 0, + }; + + startValues.current = { + ...prevValues.current, + pointerX: xSnapped, + pointerY: ySnapped, + }; + }) + .on('drag', (event: ResizeDragEvent) => { + const { nodeInternals, triggerNodeChanges } = store.getState(); + const { xSnapped, ySnapped } = getPointerPosition(event); + const node = nodeInternals.get(id); + const enableX = controlPosition.includes('right') || controlPosition.includes('left'); + const enableY = controlPosition.includes('bottom') || controlPosition.includes('top'); + const invertX = controlPosition.includes('left'); + const invertY = controlPosition.includes('top'); + + if (node) { + const changes: NodeChange[] = []; + const { + pointerX: startX, + pointerY: startY, + width: startWidth, + height: startHeight, + x: startNodeX, + y: startNodeY, + } = startValues.current; + + const { x: prevX, y: prevY, width: prevWidth, height: prevHeight } = prevValues.current; + + const distX = Math.floor(enableX ? xSnapped - startX : 0); + const distY = Math.floor(enableY ? ySnapped - startY : 0); + const width = Math.max(startWidth + (invertX ? -distX : distX), minWidth); + const height = Math.max(startHeight + (invertY ? -distY : distY), minHeight); + + const isWidthChange = width !== prevWidth; + const isHeightChange = height !== prevHeight; + + if (invertX || invertY) { + const x = invertX ? startNodeX - (width - startWidth) : startNodeX; + const y = invertY ? startNodeY - (height - startHeight) : startNodeY; + + // only transform the node if the width or height changes + const isXPosChange = x !== prevX && isWidthChange; + const isYPosChange = y !== prevY && isHeightChange; + + if (isXPosChange || isYPosChange) { + const positionChange: NodePositionChange = { + id: node.id, + type: 'position', + position: { + x: isXPosChange ? x : prevX, + y: isYPosChange ? y : prevY, + }, + }; + + changes.push(positionChange); + prevValues.current.x = positionChange.position!.x; + prevValues.current.y = positionChange.position!.y; + } + } + + if (isWidthChange || isHeightChange) { + const dimensionChange: NodeDimensionChange = { + id: id, + type: 'dimensions', + updateStyle: true, + resizing: true, + dimensions: { + width: width, + height: height, + }, + }; + changes.push(dimensionChange); + prevValues.current.width = width; + prevValues.current.height = height; + } + + triggerNodeChanges(changes); + } + }) + .on('end', () => { + const dimensionChange: NodeDimensionChange = { + id: id, + type: 'dimensions', + resizing: true, + }; + + store.getState().triggerNodeChanges([dimensionChange]); + }); + + selection.call(dragHandler); + + return () => { + selection.on('.drag', null); + }; + }, [id, controlPosition, getPointerPosition]); + + const positionClassNames = controlPosition.split('-'); + const colorStyleProp = variant === ResizeControlVariant.Line ? 'borderColor' : 'backgroundColor'; + const controlStyle = color ? { ...style, [colorStyleProp]: color } : style; + + return ( +
+ {children} +
+ ); +} + +export function ResizeControlLine(props: ResizeControlLineProps) { + return ; +} + +export default memo(ResizeControl); diff --git a/packages/node-resizer/src/index.tsx b/packages/node-resizer/src/index.tsx new file mode 100644 index 00000000..c3cd1d5d --- /dev/null +++ b/packages/node-resizer/src/index.tsx @@ -0,0 +1,4 @@ +export { default as NodeResizer } from './NodeResizer'; +export { default as NodeResizeControl } from './ResizeControl'; + +export * from './types'; diff --git a/packages/node-resizer/src/style.css b/packages/node-resizer/src/style.css new file mode 100644 index 00000000..0df9c1fb --- /dev/null +++ b/packages/node-resizer/src/style.css @@ -0,0 +1,103 @@ +.react-flow__resize-control { + position: absolute; +} + +.react-flow__resize-control.left, +.react-flow__resize-control.right { + cursor: ew-resize; +} + +.react-flow__resize-control.top, +.react-flow__resize-control.bottom { + cursor: ns-resize; +} + +.react-flow__resize-control.top.left, +.react-flow__resize-control.bottom.right { + cursor: nwse-resize; +} + +.react-flow__resize-control.bottom.left, +.react-flow__resize-control.top.right { + cursor: nesw-resize; +} + +/* handle styles */ +.react-flow__resize-control.handle { + width: 4px; + height: 4px; + border: 1px solid #fff; + border-radius: 1px; + background-color: #3367d9; + transform: translate(-50%, -50%); +} + +.react-flow__resize-control.handle.left { + left: 0; + top: 50%; +} +.react-flow__resize-control.handle.right { + left: 100%; + top: 50%; +} +.react-flow__resize-control.handle.top { + left: 50%; + top: 0; +} +.react-flow__resize-control.handle.bottom { + left: 50%; + top: 100%; +} +.react-flow__resize-control.handle.top.left { + left: 0; +} +.react-flow__resize-control.handle.bottom.left { + left: 0; +} +.react-flow__resize-control.handle.top.right { + left: 100%; +} +.react-flow__resize-control.handle.bottom.right { + left: 100%; +} + +/* line styles */ +.react-flow__resize-control.line { + border-color: #3367d9; + border-width: 0; + border-style: solid; +} + +.react-flow__resize-control.line.left, +.react-flow__resize-control.line.right { + width: 1px; + transform: translate(-50%, 0); + top: 0; + height: 100%; +} + +.react-flow__resize-control.line.left { + left: 0; + border-left-width: 1px; +} +.react-flow__resize-control.line.right { + left: 100%; + border-right-width: 1px; +} + +.react-flow__resize-control.line.top, +.react-flow__resize-control.line.bottom { + height: 1px; + transform: translate(0, -50%); + left: 0; + width: 100%; +} + +.react-flow__resize-control.line.top { + top: 0; + border-top-width: 1px; +} +.react-flow__resize-control.line.bottom { + border-bottom-width: 1px; + top: 100%; +} diff --git a/packages/node-resizer/src/types.ts b/packages/node-resizer/src/types.ts new file mode 100644 index 00000000..000ad536 --- /dev/null +++ b/packages/node-resizer/src/types.ts @@ -0,0 +1,39 @@ +import type { CSSProperties, ReactNode } from 'react'; +import type { D3DragEvent, SubjectPosition } from 'd3-drag'; + +export type NodeResizerProps = { + nodeId?: string; + color?: string; + handleClassName?: string; + handleStyle?: CSSProperties; + lineClassName?: string; + lineStyle?: CSSProperties; + isVisible?: boolean; +}; + +export type ControlLinePosition = 'top' | 'bottom' | 'left' | 'right'; + +export type ControlPosition = ControlLinePosition | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'; + +export enum ResizeControlVariant { + Line = 'line', + Handle = 'handle', +} + +export type ResizeControlProps = { + nodeId?: string; + position?: ControlPosition; + variant?: ResizeControlVariant; + color?: string; + className?: string; + style?: CSSProperties; + children?: ReactNode; + minWidth?: number; + minHeight?: number; +}; + +export type ResizeControlLineProps = ResizeControlProps & { + position: ControlLinePosition; +}; + +export type ResizeDragEvent = D3DragEvent; diff --git a/packages/node-resizer/tsconfig.json b/packages/node-resizer/tsconfig.json new file mode 100644 index 00000000..d0e5a1ce --- /dev/null +++ b/packages/node-resizer/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "@reactflow/tsconfig/react.json", + "display": "@reactflow/node-resizer", + "include": ["**/*.ts", "**/*.tsx"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/node-toolbar/CHANGELOG.md b/packages/node-toolbar/CHANGELOG.md index 7bad5ebf..624d1510 100644 --- a/packages/node-toolbar/CHANGELOG.md +++ b/packages/node-toolbar/CHANGELOG.md @@ -1,5 +1,14 @@ # @reactflow/node-toolbar +## 1.0.2 + +### Patch Changes + +- [#2626](https://github.com/wbkd/react-flow/pull/2626) [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18) - Get nodeId from React Flow context if it is not passed explicitly as prop + +- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]: + - @reactflow/core@11.3.2 + ## 1.0.1 ### Patch Changes diff --git a/packages/node-toolbar/package.json b/packages/node-toolbar/package.json index 18f05350..2163d4ce 100644 --- a/packages/node-toolbar/package.json +++ b/packages/node-toolbar/package.json @@ -1,6 +1,6 @@ { "name": "@reactflow/node-toolbar", - "version": "1.0.1", + "version": "1.0.2", "description": "A toolbar component for React Flow that can be attached to a node.", "keywords": [ "react", diff --git a/packages/node-toolbar/src/NodeToolbar.tsx b/packages/node-toolbar/src/NodeToolbar.tsx index 1dfd7669..5c6cbc86 100644 --- a/packages/node-toolbar/src/NodeToolbar.tsx +++ b/packages/node-toolbar/src/NodeToolbar.tsx @@ -8,6 +8,7 @@ import { Rect, Position, internalsSymbol, + useNodeId, } from '@reactflow/core'; import cc from 'classcat'; import shallow from 'zustand/shallow'; @@ -72,9 +73,11 @@ function NodeToolbar({ offset = 10, ...rest }: NodeToolbarProps) { + const contextNodeId = useNodeId(); + const nodesSelector = useCallback( (state: ReactFlowState): Node[] => { - const nodeIds: string[] = typeof nodeId === 'string' ? [nodeId] : nodeId; + const nodeIds = Array.isArray(nodeId) ? nodeId : [nodeId || contextNodeId || '']; return nodeIds.reduce((acc, id) => { const node = state.nodeInternals.get(id); @@ -84,7 +87,7 @@ function NodeToolbar({ return acc; }, [] as Node[]); }, - [nodeId] + [nodeId, contextNodeId] ); const nodes = useStore(nodesSelector, nodesEqualityFn); const { transform, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow); diff --git a/packages/node-toolbar/src/types.ts b/packages/node-toolbar/src/types.ts index a9f547ad..e2b0d73c 100644 --- a/packages/node-toolbar/src/types.ts +++ b/packages/node-toolbar/src/types.ts @@ -2,7 +2,7 @@ import { Position } from '@reactflow/core'; import type { HTMLAttributes } from 'react'; export type NodeToolbarProps = HTMLAttributes & { - nodeId: string | string[]; + nodeId?: string | string[]; isVisible?: boolean; position?: Position; offset?: number; diff --git a/packages/reactflow/CHANGELOG.md b/packages/reactflow/CHANGELOG.md index f3141ce1..dfeb78db 100644 --- a/packages/reactflow/CHANGELOG.md +++ b/packages/reactflow/CHANGELOG.md @@ -1,5 +1,18 @@ # reactflow +## 11.3.3 + +In this update we did some changes so that we could implement the new [``](https://reactflow.dev/docs/api/nodes/node-resizer/) component (not part of the `reactflow` package!) more smoothly. + +### Patch Changes + +- Updated dependencies [[`e6b5d90f`](https://github.com/wbkd/react-flow/commit/e6b5d90f61c8ee60e817bba232a162cae2ab3e2a), [`6ee44e07`](https://github.com/wbkd/react-flow/commit/6ee44e076eaa6908d07578a757a5187642b732ae), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`aa69c207`](https://github.com/wbkd/react-flow/commit/aa69c20765e6978f4f9c8cc63ed7110dbf6d9d9d), [`d29c401d`](https://github.com/wbkd/react-flow/commit/d29c401d598dbf2dcd5609b7adb8d029906a6f18), [`0df02f35`](https://github.com/wbkd/react-flow/commit/0df02f35f8d6c54dae36af18278feadc77acb2d6)]: + - @reactflow/core@11.3.2 + - @reactflow/minimap@11.2.3 + - @reactflow/node-toolbar@1.0.2 + - @reactflow/background@11.0.7 + - @reactflow/controls@11.0.7 + ## 11.3.2 ### Patch Changes diff --git a/packages/reactflow/package.json b/packages/reactflow/package.json index f84f948a..f1f3ecab 100644 --- a/packages/reactflow/package.json +++ b/packages/reactflow/package.json @@ -1,6 +1,6 @@ { "name": "reactflow", - "version": "11.3.2", + "version": "11.3.3", "description": "A highly customizable React library for building node-based editors and interactive flow charts", "keywords": [ "react", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d9414c1..4eafb92f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -57,6 +57,7 @@ importers: examples/vite-app: specifiers: '@cypress/skip-test': ^2.6.1 + '@reactflow/node-resizer': workspace:^0.0.0 '@types/react': ^18.0.17 '@types/react-dom': ^18.0.6 '@vitejs/plugin-react': ^2.1.0 @@ -73,6 +74,7 @@ importers: typescript: ^4.8.3 vite: ^3.1.0 dependencies: + '@reactflow/node-resizer': link:../../packages/node-resizer classcat: registry.npmjs.org/classcat/5.0.4 dagre: registry.npmjs.org/dagre/0.8.5 localforage: registry.npmjs.org/localforage/1.10.0 @@ -215,6 +217,45 @@ importers: react: registry.npmjs.org/react/18.2.0 typescript: registry.npmjs.org/typescript/4.8.3 + packages/node-resizer: + specifiers: + '@babel/runtime': ^7.18.9 + '@reactflow/core': workspace:* + '@reactflow/eslint-config': workspace:* + '@reactflow/rollup-config': workspace:* + '@reactflow/tsconfig': workspace:* + '@types/d3': ^7.4.0 + '@types/d3-drag': ^3.0.1 + '@types/d3-selection': ^3.0.3 + '@types/node': ^18.7.16 + '@types/react': ^18.0.19 + '@types/react-dom': ^18.0.6 + classcat: ^5.0.4 + d3-drag: ^3.0.0 + d3-selection: ^3.0.0 + react: ^18.2.0 + typescript: ^4.8.3 + zustand: ^4.1.1 + dependencies: + '@babel/runtime': registry.npmjs.org/@babel/runtime/7.19.0 + '@reactflow/core': link:../core + classcat: registry.npmjs.org/classcat/5.0.4 + d3-drag: registry.npmjs.org/d3-drag/3.0.0 + d3-selection: registry.npmjs.org/d3-selection/3.0.0 + zustand: registry.npmjs.org/zustand/4.1.1_react@18.2.0 + devDependencies: + '@reactflow/eslint-config': link:../../tooling/eslint-config + '@reactflow/rollup-config': link:../../tooling/rollup-config + '@reactflow/tsconfig': link:../../tooling/tsconfig + '@types/d3': registry.npmjs.org/@types/d3/7.4.0 + '@types/d3-drag': registry.npmjs.org/@types/d3-drag/3.0.1 + '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 + '@types/node': registry.npmjs.org/@types/node/18.7.16 + '@types/react': registry.npmjs.org/@types/react/18.0.19 + '@types/react-dom': registry.npmjs.org/@types/react-dom/18.0.6 + react: registry.npmjs.org/react/18.2.0 + typescript: registry.npmjs.org/typescript/4.8.3 + packages/node-toolbar: specifiers: '@babel/runtime': ^7.18.9 @@ -1380,7 +1421,6 @@ packages: resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz} name: '@types/d3-array' version: 3.0.3 - dev: false registry.npmjs.org/@types/d3-axis/3.0.1: resolution: {integrity: sha512-zji/iIbdd49g9WN0aIsGcwcTBUkgLsCSwB+uH+LPVDAiKWENMtI3cJEWt+7/YYwelMoZmbBfzA3qCdrZ2XFNnw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.1.tgz} @@ -1388,7 +1428,6 @@ packages: version: 3.0.1 dependencies: '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 - dev: false registry.npmjs.org/@types/d3-brush/3.0.1: resolution: {integrity: sha512-B532DozsiTuQMHu2YChdZU0qsFJSio3Q6jmBYGYNp3gMDzBmuFFgPt9qKA4VYuLZMp4qc6eX7IUFUEsvHiXZAw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.1.tgz} @@ -1396,19 +1435,16 @@ packages: version: 3.0.1 dependencies: '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 - dev: false registry.npmjs.org/@types/d3-chord/3.0.1: resolution: {integrity: sha512-eQfcxIHrg7V++W8Qxn6QkqBNBokyhdWSAS73AbkbMzvLQmVVBviknoz2SRS/ZJdIOmhcmmdCRE/NFOm28Z1AMw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.1.tgz} name: '@types/d3-chord' version: 3.0.1 - dev: false registry.npmjs.org/@types/d3-color/3.1.0: resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz} name: '@types/d3-color' version: 3.1.0 - dev: false registry.npmjs.org/@types/d3-contour/3.0.1: resolution: {integrity: sha512-C3zfBrhHZvrpAAK3YXqLWVAGo87A4SvJ83Q/zVJ8rFWJdKejUnDYaWZPkA8K84kb2vDA/g90LTQAz7etXcgoQQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.1.tgz} @@ -1417,19 +1453,16 @@ packages: dependencies: '@types/d3-array': registry.npmjs.org/@types/d3-array/3.0.3 '@types/geojson': registry.npmjs.org/@types/geojson/7946.0.10 - dev: false registry.npmjs.org/@types/d3-delaunay/6.0.1: resolution: {integrity: sha512-tLxQ2sfT0p6sxdG75c6f/ekqxjyYR0+LwPrsO1mbC9YDBzPJhs2HbJJRrn8Ez1DBoHRo2yx7YEATI+8V1nGMnQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.1.tgz} name: '@types/d3-delaunay' version: 6.0.1 - dev: false registry.npmjs.org/@types/d3-dispatch/3.0.1: resolution: {integrity: sha512-NhxMn3bAkqhjoxabVJWKryhnZXXYYVQxaBnbANu0O94+O/nX9qSjrA1P1jbAQJxJf+VC72TxDX/YJcKue5bRqw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.1.tgz} name: '@types/d3-dispatch' version: 3.0.1 - dev: false registry.npmjs.org/@types/d3-drag/3.0.1: resolution: {integrity: sha512-o1Va7bLwwk6h03+nSM8dpaGEYnoIG19P0lKqlic8Un36ymh9NSkNFX1yiXMKNMx8rJ0Kfnn2eovuFaL6Jvj0zA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.1.tgz} @@ -1437,19 +1470,16 @@ packages: version: 3.0.1 dependencies: '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 - dev: false registry.npmjs.org/@types/d3-dsv/3.0.0: resolution: {integrity: sha512-o0/7RlMl9p5n6FQDptuJVMxDf/7EDEv2SYEO/CwdG2tr1hTfUVi0Iavkk2ax+VpaQ/1jVhpnj5rq1nj8vwhn2A==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.0.tgz} name: '@types/d3-dsv' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-ease/3.0.0: resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz} name: '@types/d3-ease' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-fetch/3.0.1: resolution: {integrity: sha512-toZJNOwrOIqz7Oh6Q7l2zkaNfXkfR7mFSJvGvlD/Ciq/+SQ39d5gynHJZ/0fjt83ec3WL7+u3ssqIijQtBISsw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.1.tgz} @@ -1457,19 +1487,16 @@ packages: version: 3.0.1 dependencies: '@types/d3-dsv': registry.npmjs.org/@types/d3-dsv/3.0.0 - dev: false registry.npmjs.org/@types/d3-force/3.0.3: resolution: {integrity: sha512-z8GteGVfkWJMKsx6hwC3SiTSLspL98VNpmvLpEFJQpZPq6xpA1I8HNBDNSpukfK0Vb0l64zGFhzunLgEAcBWSA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.3.tgz} name: '@types/d3-force' version: 3.0.3 - dev: false registry.npmjs.org/@types/d3-format/3.0.1: resolution: {integrity: sha512-5KY70ifCCzorkLuIkDe0Z9YTf9RR2CjBX1iaJG+rgM/cPP+sO+q9YdQ9WdhQcgPj1EQiJ2/0+yUkkziTG6Lubg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.1.tgz} name: '@types/d3-format' version: 3.0.1 - dev: false registry.npmjs.org/@types/d3-geo/3.0.2: resolution: {integrity: sha512-DbqK7MLYA8LpyHQfv6Klz0426bQEf7bRTvhMy44sNGVyZoWn//B0c+Qbeg8Osi2Obdc9BLLXYAKpyWege2/7LQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.2.tgz} @@ -1477,13 +1504,11 @@ packages: version: 3.0.2 dependencies: '@types/geojson': registry.npmjs.org/@types/geojson/7946.0.10 - dev: false registry.npmjs.org/@types/d3-hierarchy/3.1.0: resolution: {integrity: sha512-g+sey7qrCa3UbsQlMZZBOHROkFqx7KZKvUpRzI/tAp/8erZWpYq7FgNKvYwebi2LaEiVs1klhUfd3WCThxmmWQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.0.tgz} name: '@types/d3-hierarchy' version: 3.1.0 - dev: false registry.npmjs.org/@types/d3-interpolate/3.0.1: resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz} @@ -1491,37 +1516,31 @@ packages: version: 3.0.1 dependencies: '@types/d3-color': registry.npmjs.org/@types/d3-color/3.1.0 - dev: false registry.npmjs.org/@types/d3-path/3.0.0: resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz} name: '@types/d3-path' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-polygon/3.0.0: resolution: {integrity: sha512-D49z4DyzTKXM0sGKVqiTDTYr+DHg/uxsiWDAkNrwXYuiZVd9o9wXZIo+YsHkifOiyBkmSWlEngHCQme54/hnHw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.0.tgz} name: '@types/d3-polygon' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-quadtree/3.0.2: resolution: {integrity: sha512-QNcK8Jguvc8lU+4OfeNx+qnVy7c0VrDJ+CCVFS9srBo2GL9Y18CnIxBdTF3v38flrGy5s1YggcoAiu6s4fLQIw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.2.tgz} name: '@types/d3-quadtree' version: 3.0.2 - dev: false registry.npmjs.org/@types/d3-random/3.0.1: resolution: {integrity: sha512-IIE6YTekGczpLYo/HehAy3JGF1ty7+usI97LqraNa8IiDur+L44d0VOjAvFQWJVdZOJHukUJw+ZdZBlgeUsHOQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.1.tgz} name: '@types/d3-random' version: 3.0.1 - dev: false registry.npmjs.org/@types/d3-scale-chromatic/3.0.0: resolution: {integrity: sha512-dsoJGEIShosKVRBZB0Vo3C8nqSDqVGujJU6tPznsBJxNJNwMF8utmS83nvCBKQYPpjCzaaHcrf66iTRpZosLPw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.0.tgz} name: '@types/d3-scale-chromatic' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-scale/4.0.2: resolution: {integrity: sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz} @@ -1529,13 +1548,11 @@ packages: version: 4.0.2 dependencies: '@types/d3-time': registry.npmjs.org/@types/d3-time/3.0.0 - dev: false registry.npmjs.org/@types/d3-selection/3.0.3: resolution: {integrity: sha512-Mw5cf6nlW1MlefpD9zrshZ+DAWL4IQ5LnWfRheW6xwsdaWOb6IRRu2H7XPAQcyXEx1D7XQWgdoKR83ui1/HlEA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.3.tgz} name: '@types/d3-selection' version: 3.0.3 - dev: false registry.npmjs.org/@types/d3-shape/3.1.0: resolution: {integrity: sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz} @@ -1543,25 +1560,21 @@ packages: version: 3.1.0 dependencies: '@types/d3-path': registry.npmjs.org/@types/d3-path/3.0.0 - dev: false registry.npmjs.org/@types/d3-time-format/4.0.0: resolution: {integrity: sha512-yjfBUe6DJBsDin2BMIulhSHmr5qNR5Pxs17+oW4DoVPyVIXZ+m6bs7j1UVKP08Emv6jRmYrYqxYzO63mQxy1rw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.0.tgz} name: '@types/d3-time-format' version: 4.0.0 - dev: false registry.npmjs.org/@types/d3-time/3.0.0: resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz} name: '@types/d3-time' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-timer/3.0.0: resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz} name: '@types/d3-timer' version: 3.0.0 - dev: false registry.npmjs.org/@types/d3-transition/3.0.2: resolution: {integrity: sha512-jo5o/Rf+/u6uerJ/963Dc39NI16FQzqwOc54bwvksGAdVfvDrqDpVeq95bEvPtBwLCVZutAEyAtmSyEMxN7vxQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.2.tgz} @@ -1569,7 +1582,6 @@ packages: version: 3.0.2 dependencies: '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 - dev: false registry.npmjs.org/@types/d3-zoom/3.0.1: resolution: {integrity: sha512-7s5L9TjfqIYQmQQEUcpMAcBOahem7TRoSO/+Gkz02GbMVuULiZzjF2BOdw291dbO2aNon4m2OdFsRGaCq2caLQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.1.tgz} @@ -1578,7 +1590,6 @@ packages: dependencies: '@types/d3-interpolate': registry.npmjs.org/@types/d3-interpolate/3.0.1 '@types/d3-selection': registry.npmjs.org/@types/d3-selection/3.0.3 - dev: false registry.npmjs.org/@types/d3/7.4.0: resolution: {integrity: sha512-jIfNVK0ZlxcuRDKtRS/SypEyOQ6UHaFQBKv032X45VvxSJ6Yi5G9behy9h6tNTHTDGh5Vq+KbmBjUWLgY4meCA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/d3/-/d3-7.4.0.tgz} @@ -1615,7 +1626,6 @@ packages: '@types/d3-timer': registry.npmjs.org/@types/d3-timer/3.0.0 '@types/d3-transition': registry.npmjs.org/@types/d3-transition/3.0.2 '@types/d3-zoom': registry.npmjs.org/@types/d3-zoom/3.0.1 - dev: false registry.npmjs.org/@types/estree/0.0.39: resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz} @@ -1633,7 +1643,6 @@ packages: resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.10.tgz} name: '@types/geojson' version: 7946.0.10 - dev: false registry.npmjs.org/@types/is-ci/3.0.0: resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==, registry: https://registry.npmjs.com/, tarball: https://registry.npmjs.org/@types/is-ci/-/is-ci-3.0.0.tgz}