From 3685420ebf0705a42ed8291d6bf44d6d29343be3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=B6ller?= Date: Thu, 24 Nov 2022 16:55:51 +0100 Subject: [PATCH 1/4] fix(subflow-node-origin): use node origin in node position calculation --- .../vite-app/src/examples/Subflow/index.tsx | 5 +- packages/core/src/hooks/useDrag/index.ts | 3 +- packages/core/src/hooks/useDrag/utils.ts | 21 ++++---- packages/core/src/store/index.ts | 9 ++-- packages/core/src/store/utils.ts | 43 +++++++++++----- packages/core/src/utils/graph.ts | 51 +++++++++++++++---- packages/minimap/src/MiniMap.tsx | 1 + 7 files changed, 97 insertions(+), 36 deletions(-) diff --git a/examples/vite-app/src/examples/Subflow/index.tsx b/examples/vite-app/src/examples/Subflow/index.tsx index 84003c83..8d2945e0 100644 --- a/examples/vite-app/src/examples/Subflow/index.tsx +++ b/examples/vite-app/src/examples/Subflow/index.tsx @@ -11,6 +11,7 @@ import ReactFlow, { Controls, MiniMap, Background, + NodeOrigin, } from 'reactflow'; import DebugNode from './DebugNode'; @@ -21,6 +22,7 @@ const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); const defaultViewport = { x: 0, y: 0, zoom: 1.5 }; +const nodeOrigin: NodeOrigin = [0.5, 0.5]; const initialNodes: Node[] = [ { @@ -90,7 +92,7 @@ const initialNodes: Node[] = [ { id: '5a', data: { label: 'Node 5a' }, - position: { x: 25, y: 50 }, + position: { x: 0, y: 0 }, className: 'light', parentNode: '5', extent: 'parent', @@ -205,6 +207,7 @@ const Subflow = () => { onlyRenderVisibleElements={false} nodeTypes={nodeTypes} fitView + nodeOrigin={nodeOrigin} > diff --git a/packages/core/src/hooks/useDrag/index.ts b/packages/core/src/hooks/useDrag/index.ts index 319c1993..47c2113a 100644 --- a/packages/core/src/hooks/useDrag/index.ts +++ b/packages/core/src/hooks/useDrag/index.ts @@ -114,6 +114,7 @@ function useDrag({ onSelectionDrag, snapGrid, snapToGrid, + nodeOrigin, } = store.getState(); const pointerPos = getPointerPosition(event); // skip events without movement @@ -133,7 +134,7 @@ function useDrag({ nextPosition.y = snapGrid[1] * Math.round(nextPosition.y / snapGrid[1]); } - const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent); + const updatedPos = calcNextPosition(n, nextPosition, nodeInternals, nodeExtent, nodeOrigin); n.position = updatedPos.position; n.positionAbsolute = updatedPos.positionAbsolute; diff --git a/packages/core/src/hooks/useDrag/utils.ts b/packages/core/src/hooks/useDrag/utils.ts index be25f3c1..f9eb6ade 100644 --- a/packages/core/src/hooks/useDrag/utils.ts +++ b/packages/core/src/hooks/useDrag/utils.ts @@ -1,7 +1,8 @@ import type { RefObject } from 'react'; import { clampPosition, devWarn } from '../../utils'; -import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, XYPosition } from '../../types'; +import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types'; +import { getNodePosition } from '../../utils/graph'; export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean { if (!node.parentNode) { @@ -60,20 +61,22 @@ export function calcNextPosition( node: NodeDragItem | Node, nextPosition: XYPosition, nodeInternals: NodeInternals, - nodeExtent?: CoordinateExtent + nodeExtent?: CoordinateExtent, + nodeOrigin?: NodeOrigin ): { position: XYPosition; positionAbsolute: XYPosition } { let currentExtent = node.extent || nodeExtent; if (node.extent === 'parent') { if (node.parentNode && node.width && node.height) { const parent = nodeInternals.get(node.parentNode); + const parentPosition = getNodePosition(parent, nodeOrigin); currentExtent = - parent?.positionAbsolute && parent?.width && parent?.height + parentPosition.positionAbsolute && parent?.width && parent?.height ? [ - [parent.positionAbsolute.x, parent.positionAbsolute.y], + [parentPosition.positionAbsolute.x, parentPosition.positionAbsolute.y], [ - parent.positionAbsolute.x + parent.width - node.width, - parent.positionAbsolute.y + parent.height - node.height, + parentPosition.positionAbsolute.x + parent.width - node.width, + parentPosition.positionAbsolute.y + parent.height - node.height, ], ] : currentExtent; @@ -84,8 +87,8 @@ export function calcNextPosition( } } else if (node.extent && node.parentNode) { const parent = nodeInternals.get(node.parentNode); - const parentX = parent?.positionAbsolute?.x ?? 0; - const parentY = parent?.positionAbsolute?.y ?? 0; + const parentPosition = getNodePosition(parent, nodeOrigin); + const { x: parentX, y: parentY } = parentPosition.positionAbsolute; currentExtent = [ [node.extent[0][0] + parentX, node.extent[0][1] + parentY], [node.extent[1][0] + parentX, node.extent[1][1] + parentY], @@ -96,7 +99,7 @@ export function calcNextPosition( if (node.parentNode) { const parentNode = nodeInternals.get(node.parentNode); - parentPosition = { x: parentNode?.positionAbsolute?.x ?? 0, y: parentNode?.positionAbsolute?.y ?? 0 }; + parentPosition = getNodePosition(parentNode, nodeOrigin).positionAbsolute; } const positionAbsolute = currentExtent diff --git a/packages/core/src/store/index.ts b/packages/core/src/store/index.ts index e09abbd7..5ffd64c3 100644 --- a/packages/core/src/store/index.ts +++ b/packages/core/src/store/index.ts @@ -23,7 +23,8 @@ const createRFStore = () => createStore((set, get) => ({ ...initialState, setNodes: (nodes: Node[]) => { - set({ nodeInternals: createNodeInternals(nodes, get().nodeInternals) }); + const { nodeInternals, nodeOrigin } = get(); + set({ nodeInternals: createNodeInternals(nodes, nodeInternals, nodeOrigin) }); }, setEdges: (edges: Edge[]) => { const { defaultEdgeOptions = {} } = get(); @@ -33,7 +34,7 @@ const createRFStore = () => const hasDefaultNodes = typeof nodes !== 'undefined'; const hasDefaultEdges = typeof edges !== 'undefined'; - const nodeInternals = hasDefaultNodes ? createNodeInternals(nodes, new Map()) : new Map(); + const nodeInternals = hasDefaultNodes ? createNodeInternals(nodes, new Map(), get().nodeOrigin) : new Map(); const nextEdges = hasDefaultEdges ? edges : []; set({ nodeInternals, edges: nextEdges, hasDefaultNodes, hasDefaultEdges }); @@ -102,7 +103,7 @@ const createRFStore = () => } }, updateNodePositions: (nodeDragItems: NodeDragItem[] | Node[], positionChanged = true, dragging = false) => { - const { onNodesChange, nodeInternals, hasDefaultNodes } = get(); + const { onNodesChange, nodeInternals, hasDefaultNodes, nodeOrigin } = get(); if (hasDefaultNodes || onNodesChange) { const changes = nodeDragItems.map((node) => { @@ -123,7 +124,7 @@ const createRFStore = () => if (changes?.length) { if (hasDefaultNodes) { const nodes = applyNodeChanges(changes, Array.from(nodeInternals.values())); - const nextNodeInternals = createNodeInternals(nodes, nodeInternals); + const nextNodeInternals = createNodeInternals(nodes, nodeInternals, nodeOrigin); set({ nodeInternals: nextNodeInternals }); } diff --git a/packages/core/src/store/utils.ts b/packages/core/src/store/utils.ts index 2af09d46..c1b4fd7e 100644 --- a/packages/core/src/store/utils.ts +++ b/packages/core/src/store/utils.ts @@ -2,7 +2,7 @@ import { zoomIdentity } from 'd3-zoom'; import type { StoreApi } from 'zustand'; import { internalsSymbol, isNumeric } from '../utils'; -import { getD3Transition, getRectOfNodes, getTransformForBounds } from '../utils/graph'; +import { getD3Transition, getRectOfNodes, getTransformForBounds, getNodePosition } from '../utils/graph'; import type { Edge, EdgeSelectionChange, @@ -12,6 +12,7 @@ import type { ReactFlowState, XYZPosition, FitViewOptions, + NodeOrigin, } from '../types'; type ParentNodes = Record; @@ -20,21 +21,33 @@ function calculateXYZPosition( node: Node, nodeInternals: NodeInternals, parentNodes: ParentNodes, - result: XYZPosition + result: XYZPosition, + nodeOrigin: NodeOrigin ): XYZPosition { if (!node.parentNode) { return result; } const parentNode = nodeInternals.get(node.parentNode)!; + const parentNodePosition = getNodePosition(parentNode, nodeOrigin); - return calculateXYZPosition(parentNode, nodeInternals, parentNodes, { - x: (result.x ?? 0) + (parentNode.position?.x ?? 0), - y: (result.y ?? 0) + (parentNode.position?.y ?? 0), - z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0, - }); + return calculateXYZPosition( + parentNode, + nodeInternals, + parentNodes, + { + x: (result.x ?? 0) + parentNodePosition.x, + y: (result.y ?? 0) + parentNodePosition.y, + z: (parentNode[internalsSymbol]?.z ?? 0) > (result.z ?? 0) ? parentNode[internalsSymbol]?.z ?? 0 : result.z ?? 0, + }, + nodeOrigin + ); } -export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals): NodeInternals { +export function createNodeInternals( + nodes: Node[], + nodeInternals: NodeInternals, + nodeOrigin: NodeOrigin +): NodeInternals { const nextNodeInternals = new Map(); const parentNodes: ParentNodes = {}; @@ -74,10 +87,16 @@ export function createNodeInternals(nodes: Node[], nodeInternals: NodeInternals) } if (node.parentNode || parentNodes[node.id]) { - const { x, y, z } = calculateXYZPosition(node, nextNodeInternals, parentNodes, { - ...node.position, - z: node[internalsSymbol]?.z ?? 0, - }); + const { x, y, z } = calculateXYZPosition( + node, + nextNodeInternals, + parentNodes, + { + ...node.position, + z: node[internalsSymbol]?.z ?? 0, + }, + nodeOrigin + ); node.positionAbsolute = { x, diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index 0408603f..d6a4d068 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -141,23 +141,55 @@ export const pointToRendererPoint = ( return position; }; +export const getNodePosition = ( + node: Node | undefined, + nodeOrigin: NodeOrigin = [0, 0] +): XYPosition & { positionAbsolute: XYPosition } => { + if (!node) { + return { + x: 0, + y: 0, + positionAbsolute: { + x: 0, + y: 0, + }, + }; + } + + const position: XYPosition = { + x: node.position.x - (node.width ?? 0) * nodeOrigin[0], + y: node.position.y - (node.height ?? 0) * nodeOrigin[1], + }; + + const positionAbsolute: XYPosition = { + x: (node.positionAbsolute?.x ?? 0) - (node.width ?? 0) * nodeOrigin[0], + y: (node.positionAbsolute?.y ?? 0) - (node.height ?? 0) * nodeOrigin[1], + }; + + return { + ...position, + positionAbsolute, + }; +}; + export const getRectOfNodes = (nodes: Node[], nodeOrigin: NodeOrigin = [0, 0]): Rect => { if (nodes.length === 0) { return { x: 0, y: 0, width: 0, height: 0 }; } const box = nodes.reduce( - (currBox, { positionAbsolute, position, width, height }) => { + (currBox, node) => { + const { positionAbsolute, ...position } = getNodePosition(node, nodeOrigin); const nodeX = positionAbsolute ? positionAbsolute.x : position.x; const nodeY = positionAbsolute ? positionAbsolute.y : position.y; return getBoundsOfBoxes( currBox, rectToBox({ - x: nodeX - nodeOrigin[0] * (width || 0), - y: nodeY - nodeOrigin[1] * (height || 0), - width: width || 0, - height: height || 0, + x: nodeX, + y: nodeY, + width: node.width || 0, + height: node.height || 0, }) ); }, @@ -186,15 +218,17 @@ export const getNodesInside = ( const visibleNodes: Node[] = []; nodeInternals.forEach((node) => { - const { width, height, selectable = true, positionAbsolute = { x: 0, y: 0 } } = node; + const { width, height, selectable = true } = node; if (excludeNonSelectableNodes && !selectable) { return false; } + const { positionAbsolute } = getNodePosition(node, nodeOrigin); + const nodeRect = { - x: positionAbsolute.x - nodeOrigin[0] * (width || 0), - y: positionAbsolute.y - nodeOrigin[1] * (height || 0), + x: positionAbsolute.x, + y: positionAbsolute.y, width: width || 0, height: height || 0, }; @@ -243,4 +277,3 @@ export const getTransformForBounds = ( export const getD3Transition = (selection: D3Selection, duration = 0) => { return selection.transition().duration(duration); }; - diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 4764a987..8d804f4e 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -162,6 +162,7 @@ function MiniMap({ {nodes.map((node) => ( Date: Thu, 24 Nov 2022 22:36:00 +0100 Subject: [PATCH 2/4] fix(subflow-node-origin): use helper function in minimap, calculate extent corrrectly when using nodeOrigin and subflows --- .../vite-app/src/examples/Subflow/index.tsx | 2 - packages/core/src/hooks/useDrag/utils.ts | 19 ++++--- packages/core/src/index.ts | 1 + packages/core/src/store/utils.ts | 4 +- packages/core/src/utils/graph.ts | 6 +-- packages/minimap/src/MiniMap.tsx | 50 +++++++++++-------- 6 files changed, 47 insertions(+), 35 deletions(-) diff --git a/examples/vite-app/src/examples/Subflow/index.tsx b/examples/vite-app/src/examples/Subflow/index.tsx index 8d2945e0..55aea018 100644 --- a/examples/vite-app/src/examples/Subflow/index.tsx +++ b/examples/vite-app/src/examples/Subflow/index.tsx @@ -22,7 +22,6 @@ const onNodeClick = (_: MouseEvent, node: Node) => console.log('click', node); const onEdgeClick = (_: MouseEvent, edge: Edge) => console.log('click', edge); const defaultViewport = { x: 0, y: 0, zoom: 1.5 }; -const nodeOrigin: NodeOrigin = [0.5, 0.5]; const initialNodes: Node[] = [ { @@ -207,7 +206,6 @@ const Subflow = () => { onlyRenderVisibleElements={false} nodeTypes={nodeTypes} fitView - nodeOrigin={nodeOrigin} > diff --git a/packages/core/src/hooks/useDrag/utils.ts b/packages/core/src/hooks/useDrag/utils.ts index f9eb6ade..61b0d3f9 100644 --- a/packages/core/src/hooks/useDrag/utils.ts +++ b/packages/core/src/hooks/useDrag/utils.ts @@ -2,7 +2,7 @@ import type { RefObject } from 'react'; import { clampPosition, devWarn } from '../../utils'; import type { CoordinateExtent, Node, NodeDragItem, NodeInternals, NodeOrigin, XYPosition } from '../../types'; -import { getNodePosition } from '../../utils/graph'; +import { getNodePositionWithOrigin } from '../../utils/graph'; export function isParentSelected(node: Node, nodeInternals: NodeInternals): boolean { if (!node.parentNode) { @@ -62,21 +62,24 @@ export function calcNextPosition( nextPosition: XYPosition, nodeInternals: NodeInternals, nodeExtent?: CoordinateExtent, - nodeOrigin?: NodeOrigin + nodeOrigin: NodeOrigin = [0, 0] ): { position: XYPosition; positionAbsolute: XYPosition } { let currentExtent = node.extent || nodeExtent; if (node.extent === 'parent') { if (node.parentNode && node.width && node.height) { const parent = nodeInternals.get(node.parentNode); - const parentPosition = getNodePosition(parent, nodeOrigin); + const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin); currentExtent = parentPosition.positionAbsolute && parent?.width && parent?.height ? [ - [parentPosition.positionAbsolute.x, parentPosition.positionAbsolute.y], [ - parentPosition.positionAbsolute.x + parent.width - node.width, - parentPosition.positionAbsolute.y + parent.height - node.height, + 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], ], ] : currentExtent; @@ -87,7 +90,7 @@ export function calcNextPosition( } } else if (node.extent && node.parentNode) { const parent = nodeInternals.get(node.parentNode); - const parentPosition = getNodePosition(parent, nodeOrigin); + const parentPosition = getNodePositionWithOrigin(parent, nodeOrigin); const { x: parentX, y: parentY } = parentPosition.positionAbsolute; currentExtent = [ [node.extent[0][0] + parentX, node.extent[0][1] + parentY], @@ -99,7 +102,7 @@ export function calcNextPosition( if (node.parentNode) { const parentNode = nodeInternals.get(node.parentNode); - parentPosition = getNodePosition(parentNode, nodeOrigin).positionAbsolute; + parentPosition = getNodePositionWithOrigin(parentNode, nodeOrigin).positionAbsolute; } const positionAbsolute = currentExtent diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e2e077db..d51540e9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -19,6 +19,7 @@ export { updateEdge, getTransformForBounds, getRectOfNodes, + getNodePositionWithOrigin, } from './utils/graph'; export { applyNodeChanges, applyEdgeChanges } from './utils/changes'; export { getMarkerEnd } from './components/Edges/utils'; diff --git a/packages/core/src/store/utils.ts b/packages/core/src/store/utils.ts index c1b4fd7e..02a4516b 100644 --- a/packages/core/src/store/utils.ts +++ b/packages/core/src/store/utils.ts @@ -2,7 +2,7 @@ import { zoomIdentity } from 'd3-zoom'; import type { StoreApi } from 'zustand'; import { internalsSymbol, isNumeric } from '../utils'; -import { getD3Transition, getRectOfNodes, getTransformForBounds, getNodePosition } from '../utils/graph'; +import { getD3Transition, getRectOfNodes, getTransformForBounds, getNodePositionWithOrigin } from '../utils/graph'; import type { Edge, EdgeSelectionChange, @@ -28,7 +28,7 @@ function calculateXYZPosition( return result; } const parentNode = nodeInternals.get(node.parentNode)!; - const parentNodePosition = getNodePosition(parentNode, nodeOrigin); + const parentNodePosition = getNodePositionWithOrigin(parentNode, nodeOrigin); return calculateXYZPosition( parentNode, diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index d6a4d068..f07267a5 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -141,7 +141,7 @@ export const pointToRendererPoint = ( return position; }; -export const getNodePosition = ( +export const getNodePositionWithOrigin = ( node: Node | undefined, nodeOrigin: NodeOrigin = [0, 0] ): XYPosition & { positionAbsolute: XYPosition } => { @@ -179,7 +179,7 @@ export const getRectOfNodes = (nodes: Node[], nodeOrigin: NodeOrigin = [0, 0]): const box = nodes.reduce( (currBox, node) => { - const { positionAbsolute, ...position } = getNodePosition(node, nodeOrigin); + const { positionAbsolute, ...position } = getNodePositionWithOrigin(node, nodeOrigin); const nodeX = positionAbsolute ? positionAbsolute.x : position.x; const nodeY = positionAbsolute ? positionAbsolute.y : position.y; @@ -224,7 +224,7 @@ export const getNodesInside = ( return false; } - const { positionAbsolute } = getNodePosition(node, nodeOrigin); + const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin); const nodeRect = { x: positionAbsolute.x, diff --git a/packages/minimap/src/MiniMap.tsx b/packages/minimap/src/MiniMap.tsx index 8d804f4e..890df176 100644 --- a/packages/minimap/src/MiniMap.tsx +++ b/packages/minimap/src/MiniMap.tsx @@ -7,7 +7,14 @@ import shallow from 'zustand/shallow'; import { zoom, zoomIdentity } from 'd3-zoom'; import type { D3ZoomEvent } from 'd3-zoom'; import { select, pointer } from 'd3-selection'; -import { useStore, getRectOfNodes, Panel, getBoundsOfRects, useStoreApi } from '@reactflow/core'; +import { + useStore, + getRectOfNodes, + Panel, + getBoundsOfRects, + useStoreApi, + getNodePositionWithOrigin, +} from '@reactflow/core'; import type { ReactFlowState, Rect } from '@reactflow/core'; import MiniMapNode from './MiniMapNode'; @@ -159,25 +166,28 @@ function MiniMap({ onClick={onSvgClick} > {ariaLabel && {ariaLabel}} - {nodes.map((node) => ( - - ))} + {nodes.map((node) => { + const { positionAbsolute } = getNodePositionWithOrigin(node, nodeOrigin); + + return ( + + ); + })} Date: Thu, 24 Nov 2022 22:38:05 +0100 Subject: [PATCH 3/4] chore(subflow-node-origin): create changeset --- .changeset/neat-fishes-burn.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/neat-fishes-burn.md diff --git a/.changeset/neat-fishes-burn.md b/.changeset/neat-fishes-burn.md new file mode 100644 index 00000000..c771c907 --- /dev/null +++ b/.changeset/neat-fishes-burn.md @@ -0,0 +1,6 @@ +--- +'@reactflow/core': patch +'@reactflow/minimap': patch +--- + +Fix and improve the behaviour when using nodeOrigin in combination with subflows From a8d590a60be23437e1d0a87a5d7a6f6ba9d86be9 Mon Sep 17 00:00:00 2001 From: moklick Date: Fri, 25 Nov 2022 21:25:00 +0100 Subject: [PATCH 4/4] chore(nodeOriginHelper): cleanup --- packages/core/src/utils/graph.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/core/src/utils/graph.ts b/packages/core/src/utils/graph.ts index f07267a5..ea390967 100644 --- a/packages/core/src/utils/graph.ts +++ b/packages/core/src/utils/graph.ts @@ -156,19 +156,18 @@ export const getNodePositionWithOrigin = ( }; } - const position: XYPosition = { - x: node.position.x - (node.width ?? 0) * nodeOrigin[0], - y: node.position.y - (node.height ?? 0) * nodeOrigin[1], - }; - - const positionAbsolute: XYPosition = { - x: (node.positionAbsolute?.x ?? 0) - (node.width ?? 0) * nodeOrigin[0], - y: (node.positionAbsolute?.y ?? 0) - (node.height ?? 0) * nodeOrigin[1], + const offset: XYPosition = { + x: (node.width ?? 0) * nodeOrigin[0], + y: (node.height ?? 0) * nodeOrigin[1], }; return { - ...position, - positionAbsolute, + 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, + }, }; };