feat(nodes, edges, store): Add updateNodeDimensions actions
* Returns new changes for updating node dimensions * Add expandParent prop to nodes when parents should be expanded to fit the new child in them *
This commit is contained in:
@@ -94,25 +94,15 @@ const onDragStop: DraggableEventListener = ({ event, data: { deltaX, deltaY } })
|
||||
store.hooks.nodeDragStop.trigger({ event, node: node.value })
|
||||
}
|
||||
|
||||
const updatePosition = (width: number, height: number) => {
|
||||
const handleBounds = getHandleBounds(nodeElement.value, scale.value, id)
|
||||
node.value.dimensions = {
|
||||
width,
|
||||
height,
|
||||
}
|
||||
node.value.handleBounds = handleBounds
|
||||
}
|
||||
|
||||
const removeEmpty = (obj: Record<string, any>) => Object.fromEntries(Object.entries(obj).filter(([_, v]) => v != null))
|
||||
|
||||
store.updateNodePosition({ id: node.value.id, diff: { x: 0, y: 0 } })
|
||||
onMounted(() => {
|
||||
const dimensions = ref(getDimensions(nodeElement.value))
|
||||
useResizeObserver(nodeElement, () => (dimensions.value = getDimensions(nodeElement.value)))
|
||||
useResizeObserver(nodeElement, () => store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value, forceUpdate: true }]))
|
||||
watch([() => node.value.type, () => node.value.sourcePosition, () => node.value.targetPosition], () =>
|
||||
nextTick(() => updatePosition(dimensions.value.width, dimensions.value.height)),
|
||||
nextTick(() => store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value }])),
|
||||
)
|
||||
watch(dimensions, ({ width: w, height: h }) => nextTick(() => w > 0 && h > 0 && updatePosition(w, h)), { immediate: true })
|
||||
store.updateNodeDimensions([{ id: node.value.id, nodeElement: nodeElement.value }])
|
||||
})
|
||||
</script>
|
||||
<script lang="ts">
|
||||
|
||||
@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<FlowProps>(), {
|
||||
applyDefault: undefined,
|
||||
fitViewOnInit: undefined,
|
||||
})
|
||||
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:elements', 'update:edges', 'update:nodes'])
|
||||
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:edges', 'update:nodes'])
|
||||
|
||||
const {
|
||||
id,
|
||||
|
||||
+35
-1
@@ -12,13 +12,16 @@ import {
|
||||
Edge,
|
||||
Connection,
|
||||
GraphEdge,
|
||||
NodePositionChange,
|
||||
} from '~/types'
|
||||
import {
|
||||
applyChanges,
|
||||
connectionExists,
|
||||
createPositionChange,
|
||||
createSelectionChange,
|
||||
getDimensions,
|
||||
getEdgeId,
|
||||
getHandleBounds,
|
||||
getSelectionChanges,
|
||||
isEdge,
|
||||
isGraphEdge,
|
||||
@@ -110,7 +113,7 @@ export const parseChildren = (
|
||||
|
||||
export default (state: State, getters: ComputedGetters): Actions => {
|
||||
const updateNodePosition: Actions['updateNodePosition'] = ({ id, diff = { x: 0, y: 0 }, dragging }) => {
|
||||
const changes: NodeDimensionChange[] = []
|
||||
const changes: NodePositionChange[] = []
|
||||
|
||||
state.nodes.forEach((node) => {
|
||||
if (node.selected) {
|
||||
@@ -126,6 +129,36 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
||||
|
||||
if (changes.length) state.hooks.nodesChange.trigger(changes)
|
||||
}
|
||||
const updateNodeDimensions: Actions['updateNodeDimensions'] = (updates) => {
|
||||
const changes: NodeDimensionChange[] = updates.reduce<NodeDimensionChange[]>((res, update) => {
|
||||
const node = getters.getNode.value(update.id)
|
||||
|
||||
if (node) {
|
||||
const dimensions = getDimensions(update.nodeElement)
|
||||
const doUpdate = !!(
|
||||
dimensions.width &&
|
||||
dimensions.height &&
|
||||
(node.dimensions.width !== dimensions.width || node.dimensions.height !== dimensions.height || update.forceUpdate)
|
||||
)
|
||||
|
||||
if (doUpdate) {
|
||||
const handleBounds = getHandleBounds(update.nodeElement, state.transform[2])
|
||||
node.dimensions = dimensions
|
||||
node.handleBounds = handleBounds
|
||||
|
||||
res.push({
|
||||
id: node.id,
|
||||
type: 'dimensions',
|
||||
dimensions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}, [])
|
||||
|
||||
if (changes.length) state.hooks.nodesChange.trigger(changes)
|
||||
}
|
||||
const addSelectedNodes: Actions['addSelectedNodes'] = (nodes) => {
|
||||
const selectedNodesIds = nodes.map((n) => n.id)
|
||||
|
||||
@@ -296,6 +329,7 @@ export default (state: State, getters: ComputedGetters): Actions => {
|
||||
}
|
||||
return {
|
||||
updateNodePosition,
|
||||
updateNodeDimensions,
|
||||
setElements,
|
||||
setNodes,
|
||||
setEdges,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { XYPosition, Dimensions } from './flow'
|
||||
import { NodeHandleBounds, Node, GraphNode, CoordinateExtent } from './node'
|
||||
import { Edge } from './edge'
|
||||
|
||||
export type CreatePositionChangeParams = {
|
||||
node: GraphNode
|
||||
nodeExtent: CoordinateExtent
|
||||
diff?: XYPosition
|
||||
dragging?: boolean
|
||||
}
|
||||
|
||||
export type NodeDimensionChange = {
|
||||
id: string
|
||||
type: 'dimensions'
|
||||
dimensions: Dimensions
|
||||
handleBounds?: NodeHandleBounds
|
||||
}
|
||||
|
||||
export type NodePositionChange = {
|
||||
id: string
|
||||
type: 'position'
|
||||
position?: XYPosition
|
||||
dragging?: boolean
|
||||
}
|
||||
|
||||
export type NodeSelectionChange = {
|
||||
id: string
|
||||
type: 'select'
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
export type NodeRemoveChange = {
|
||||
id: string
|
||||
type: 'remove'
|
||||
}
|
||||
|
||||
export type NodeAddChange<NodeData = any> = {
|
||||
item: Node<NodeData>
|
||||
type: 'add'
|
||||
}
|
||||
|
||||
export type NodeResetChange<NodeData = any> = {
|
||||
item: Node<NodeData>
|
||||
type: 'reset'
|
||||
}
|
||||
|
||||
export type NodeChange =
|
||||
| NodeDimensionChange
|
||||
| NodePositionChange
|
||||
| NodeSelectionChange
|
||||
| NodeRemoveChange
|
||||
| NodeAddChange
|
||||
| NodeResetChange
|
||||
|
||||
export type EdgeSelectionChange = NodeSelectionChange
|
||||
export type EdgeRemoveChange = NodeRemoveChange
|
||||
export type EdgeAddChange<EdgeData = any> = {
|
||||
item: Edge<EdgeData>
|
||||
type: 'add'
|
||||
}
|
||||
export type EdgeResetChange<EdgeData = any> = {
|
||||
item: Edge<EdgeData>
|
||||
type: 'reset'
|
||||
}
|
||||
export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange | EdgeAddChange | EdgeResetChange
|
||||
export type ElementChange = NodeChange | EdgeChange
|
||||
@@ -76,6 +76,11 @@ export interface Edge<T = any> extends Element<T> {
|
||||
selectable?: boolean
|
||||
}
|
||||
|
||||
export type DefaultEdgeOptions = Omit<
|
||||
Edge,
|
||||
'id' | 'source' | 'target' | 'sourceHandle' | 'targetHandle' | 'sourceNode' | 'targetNode'
|
||||
>
|
||||
|
||||
export interface EdgePositions {
|
||||
sourceX: number
|
||||
sourceY: number
|
||||
@@ -125,6 +130,24 @@ export interface EdgeProps<Data = any> {
|
||||
updatable?: boolean
|
||||
markerStart?: string
|
||||
markerEnd?: string
|
||||
curvature?: number
|
||||
}
|
||||
|
||||
export type BaseEdgeProps = Pick<
|
||||
EdgeProps,
|
||||
| 'label'
|
||||
| 'labelStyle'
|
||||
| 'labelShowBg'
|
||||
| 'labelBgStyle'
|
||||
| 'labelBgPadding'
|
||||
| 'labelBgBorderRadius'
|
||||
| 'style'
|
||||
| 'markerStart'
|
||||
| 'markerEnd'
|
||||
> & {
|
||||
centerX: number
|
||||
centerY: number
|
||||
path: string
|
||||
}
|
||||
|
||||
export interface SmoothStepEdgeProps<Data = any> extends EdgeProps<Data> {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { ConnectionLineType, ConnectionMode } from './connection'
|
||||
import { KeyCode, PanOnScrollMode, UseZoomPanHelper } from './zoom'
|
||||
import { Actions, ComputedGetters, State, FlowStore } from './store'
|
||||
import { FlowHooksOn } from './hooks'
|
||||
import { DefaultEdgeTypes, DefaultNodeTypes, EdgeComponent, NodeComponent } from "~/types/components";
|
||||
import { DefaultEdgeTypes, DefaultNodeTypes, EdgeComponent, NodeComponent } from './components'
|
||||
|
||||
export type FlowElement<N = any, E = any> = GraphNode<N> | GraphEdge<E>
|
||||
export type FlowElements<N = any, E = any> = FlowElement<N, E>[]
|
||||
|
||||
+2
-34
@@ -2,42 +2,10 @@ import { EventHook, EventHookOn } from '@vueuse/core'
|
||||
import { MouseTouchEvent } from '@braks/revue-draggable'
|
||||
import { FlowInstance, Dimensions, XYPosition } from './flow'
|
||||
import { GraphEdge } from './edge'
|
||||
import { CoordinateExtent, GraphNode, HandleBounds } from './node'
|
||||
import { GraphNode } from './node'
|
||||
import { Connection, OnConnectStartParams } from './connection'
|
||||
import { FlowTransform } from './zoom'
|
||||
|
||||
export type NodeDimensionChange = {
|
||||
id: string
|
||||
type: 'dimensions'
|
||||
dimensions?: Dimensions
|
||||
position?: XYPosition
|
||||
handleBounds?: HandleBounds
|
||||
dragging?: boolean
|
||||
}
|
||||
export type NodeSelectionChange = {
|
||||
id: string
|
||||
type: 'select'
|
||||
selected: boolean
|
||||
}
|
||||
export type NodeRemoveChange = {
|
||||
id: string
|
||||
type: 'remove'
|
||||
}
|
||||
export type NodeChange = NodeDimensionChange | NodeSelectionChange | NodeRemoveChange
|
||||
|
||||
export type EdgeSelectionChange = NodeSelectionChange
|
||||
export type EdgeRemoveChange = NodeRemoveChange
|
||||
export type EdgeChange = EdgeSelectionChange | EdgeRemoveChange
|
||||
|
||||
export type SelectionChange = EdgeSelectionChange | NodeSelectionChange
|
||||
export type RemoveChange = EdgeRemoveChange | NodeRemoveChange
|
||||
|
||||
export type CreatePositionChangeParams = {
|
||||
node: GraphNode
|
||||
nodeExtent: CoordinateExtent
|
||||
diff?: XYPosition
|
||||
dragging?: boolean
|
||||
}
|
||||
import { EdgeChange, NodeChange } from './changes'
|
||||
|
||||
export interface FlowEvents<N = any, E = N> {
|
||||
nodesChange: NodeChange[]
|
||||
|
||||
@@ -6,3 +6,4 @@ export * from './node'
|
||||
export * from './zoom'
|
||||
export * from './store'
|
||||
export * from './hooks'
|
||||
export * from './changes'
|
||||
|
||||
+28
-18
@@ -1,16 +1,17 @@
|
||||
import { CSSProperties } from 'vue'
|
||||
import { XYPosition, Position, SnapGrid, Element, XYZPosition, Dimensions } from './flow'
|
||||
import { HandleElement, ValidConnectionFunc } from './components'
|
||||
import { DefaultNodeTypes, HandleElement, ValidConnectionFunc } from './components'
|
||||
|
||||
export type CoordinateExtent = [[number, number], [number, number]]
|
||||
|
||||
export type HandleBounds = {
|
||||
export type NodeHandleBounds = {
|
||||
source?: HandleElement[]
|
||||
target?: HandleElement[]
|
||||
}
|
||||
|
||||
export interface Node<T = any> extends Element<T> {
|
||||
position: XYPosition
|
||||
type?: keyof DefaultNodeTypes | string
|
||||
targetPosition?: Position
|
||||
sourcePosition?: Position
|
||||
draggable?: boolean
|
||||
@@ -21,29 +22,36 @@ export interface Node<T = any> extends Element<T> {
|
||||
isValidTargetPos?: ValidConnectionFunc
|
||||
isValidSourcePos?: ValidConnectionFunc
|
||||
extent?: 'parent' | CoordinateExtent
|
||||
expandParent?: boolean
|
||||
children?: Node<T>[]
|
||||
dimensions?: Dimensions
|
||||
}
|
||||
|
||||
export interface GraphNode<T = any> extends Node<T> {
|
||||
export interface GraphNode<T = any, P = any> extends Node<T> {
|
||||
computedPosition: XYZPosition
|
||||
handleBounds: HandleBounds
|
||||
handleBounds: NodeHandleBounds
|
||||
dimensions: Dimensions
|
||||
parentNode?: GraphNode<T>
|
||||
isParent?: boolean
|
||||
selected?: boolean
|
||||
dragging?: boolean
|
||||
isParent: boolean
|
||||
selected: boolean
|
||||
dragging: boolean
|
||||
parentNode?: GraphNode<P extends infer U ? U : never>
|
||||
}
|
||||
|
||||
export interface NodeProps<T = any> {
|
||||
id: string
|
||||
dimensions: Dimensions
|
||||
handleBounds: HandleBounds
|
||||
handleBounds: NodeHandleBounds
|
||||
/** computed position is the position relative to the node's extent (i.e. parent or CoordinateExtent) */
|
||||
computedPosition: XYZPosition
|
||||
/** x and y position on the graph */
|
||||
position: XYPosition
|
||||
draggable?: boolean
|
||||
selectable?: boolean
|
||||
connectable?: boolean
|
||||
draggable: boolean
|
||||
selectable: boolean
|
||||
connectable: boolean
|
||||
selected: boolean
|
||||
dragging: boolean
|
||||
hidden: boolean
|
||||
nodeElement: HTMLDivElement
|
||||
zIndex: number
|
||||
label?:
|
||||
| string
|
||||
| {
|
||||
@@ -52,17 +60,19 @@ export interface NodeProps<T = any> {
|
||||
}
|
||||
class?: string
|
||||
style?: CSSProperties
|
||||
hidden?: boolean
|
||||
type?: string
|
||||
data?: T
|
||||
selected?: boolean
|
||||
targetPosition?: Position
|
||||
sourcePosition?: Position
|
||||
dragging?: boolean
|
||||
isValidTargetPos?: ValidConnectionFunc
|
||||
isValidSourcePos?: ValidConnectionFunc
|
||||
parentNode?: any
|
||||
parentNode?: string
|
||||
isParent?: boolean
|
||||
children?: Node<T>[]
|
||||
nodeElement: HTMLDivElement
|
||||
dragHandle?: string
|
||||
}
|
||||
|
||||
export type NodeBounds = XYPosition & {
|
||||
width: number | null
|
||||
height: number | null
|
||||
}
|
||||
|
||||
+9
-1
@@ -5,7 +5,8 @@ import { Connection, ConnectionLineType, ConnectionMode, SetConnectionId } from
|
||||
import { Edge, GraphEdge } from './edge'
|
||||
import { GraphNode, CoordinateExtent, Node } from './node'
|
||||
import { D3Selection, D3Zoom, D3ZoomHandler, KeyCode, PanOnScrollMode } from './zoom'
|
||||
import { EdgeChange, FlowHooks, FlowHooksOn, NodeChange } from './hooks'
|
||||
import { FlowHooks, FlowHooksOn } from './hooks'
|
||||
import { NodeChange, EdgeChange } from './changes'
|
||||
|
||||
export interface State<N = any, E = N> extends Omit<FlowOptions<N, E>, 'id' | 'modelValue'> {
|
||||
hooks: FlowHooks<N, E>
|
||||
@@ -91,6 +92,13 @@ export interface Actions<N = any, E = N> {
|
||||
setInteractive: (isInteractive: boolean) => void
|
||||
setState: (state: Partial<FlowOptions<N, E>>) => void
|
||||
updateNodePosition: ({ id, diff, dragging }: { id?: string; diff?: XYPosition; dragging?: boolean }) => void
|
||||
updateNodeDimensions: (
|
||||
update: {
|
||||
id: string
|
||||
nodeElement: HTMLDivElement
|
||||
forceUpdate?: boolean
|
||||
}[],
|
||||
) => void
|
||||
$reset: () => void
|
||||
}
|
||||
|
||||
|
||||
+75
-27
@@ -1,61 +1,109 @@
|
||||
import {
|
||||
CreatePositionChangeParams,
|
||||
EdgeChange,
|
||||
EdgeSelectionChange,
|
||||
ElementChange,
|
||||
FlowElement,
|
||||
FlowElements,
|
||||
GraphNode,
|
||||
NodeChange,
|
||||
NodeDimensionChange,
|
||||
SelectionChange,
|
||||
NodeSelectionChange,
|
||||
NodePositionChange,
|
||||
} from '~/types'
|
||||
import { clampPosition, isGraphNode } from '~/utils/graph'
|
||||
|
||||
function handleParentExpand(parent: GraphNode, updateItem: GraphNode) {
|
||||
if (parent) {
|
||||
const extendWidth = updateItem.position.x + updateItem.dimensions.width - parent.dimensions.width
|
||||
const extendHeight = updateItem.position.y + updateItem.dimensions.height - parent.dimensions.height
|
||||
|
||||
if (extendWidth > 0 || extendHeight > 0 || updateItem.position.x < 0 || updateItem.position.y < 0) {
|
||||
parent.style = { ...parent.style } || {}
|
||||
|
||||
if (extendWidth > 0) {
|
||||
if (!parent.style.width) {
|
||||
parent.style.width = parent.dimensions.width
|
||||
}
|
||||
;(<number>parent.style.width) += extendWidth
|
||||
}
|
||||
|
||||
if (extendHeight > 0) {
|
||||
if (!parent.style.height) {
|
||||
parent.style.height = parent.dimensions.height
|
||||
}
|
||||
;(<number>parent.style.height) += extendHeight
|
||||
}
|
||||
|
||||
if (updateItem.position.x < 0) {
|
||||
const xDiff = Math.abs(updateItem.position.x)
|
||||
parent.position.x = parent.position.x - xDiff
|
||||
;(<number>parent.style.width) += xDiff
|
||||
updateItem.position.x = 0
|
||||
}
|
||||
|
||||
if (updateItem.position.y < 0) {
|
||||
const yDiff = Math.abs(updateItem.position.y)
|
||||
parent.position.y = parent.position.y - yDiff
|
||||
;(<number>parent.style.height) += yDiff
|
||||
updateItem.position.y = 0
|
||||
}
|
||||
|
||||
parent.dimensions.width = <number>parent.style.width
|
||||
parent.dimensions.height = <number>parent.style.height
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const applyChanges = <
|
||||
T extends FlowElement = GraphNode,
|
||||
C extends NodeChange = T extends GraphNode ? NodeChange : EdgeChange,
|
||||
C extends ElementChange = T extends GraphNode ? NodeChange : EdgeChange,
|
||||
>(
|
||||
changes: C[],
|
||||
elements: T[],
|
||||
): T[] => {
|
||||
let elementIds = elements.map((el) => el.id)
|
||||
changes.forEach((change) => {
|
||||
const i = elementIds.indexOf(change.id)
|
||||
if (change.type === 'add') return elements.push(change.item as any)
|
||||
const i = elementIds.indexOf((<any>change).id)
|
||||
const el = elements[i]
|
||||
if (el) {
|
||||
switch (change.type) {
|
||||
case 'select':
|
||||
el.selected = change.selected
|
||||
break
|
||||
case 'dimensions':
|
||||
if (isGraphNode(el)) {
|
||||
if (typeof change.dimensions !== 'undefined') el.dimensions = change.dimensions
|
||||
if (typeof change.position !== 'undefined') el.position = change.position
|
||||
if (typeof change.dragging !== 'undefined') el.dragging = change.dragging
|
||||
}
|
||||
break
|
||||
case 'remove':
|
||||
elements.splice(i, 1)
|
||||
elementIds = elements.map((el) => el.id)
|
||||
break
|
||||
}
|
||||
switch (change.type) {
|
||||
case 'select':
|
||||
el.selected = change.selected
|
||||
break
|
||||
case 'position':
|
||||
if (isGraphNode(el)) {
|
||||
if (typeof change.position !== 'undefined') el.position = change.position
|
||||
if (typeof change.dragging !== 'undefined') el.dragging = change.dragging
|
||||
if (el.expandParent && el.parentNode) handleParentExpand(el, el.parentNode)
|
||||
}
|
||||
break
|
||||
case 'dimensions':
|
||||
if (isGraphNode(el)) {
|
||||
if (typeof change.dimensions !== 'undefined') el.dimensions = change.dimensions
|
||||
if (el.expandParent && el.parentNode) handleParentExpand(el, el.parentNode)
|
||||
}
|
||||
break
|
||||
case 'remove':
|
||||
elements.splice(i, 1)
|
||||
elementIds = elements.map((el) => el.id)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
export const createSelectionChange = (id: string, selected: boolean): SelectionChange => ({
|
||||
export const createSelectionChange = (id: string, selected: boolean): NodeSelectionChange | EdgeSelectionChange => ({
|
||||
id,
|
||||
type: 'select',
|
||||
selected,
|
||||
})
|
||||
|
||||
export const createPositionChange = ({ node, diff, dragging, nodeExtent }: CreatePositionChangeParams): NodeDimensionChange => {
|
||||
const change: NodeDimensionChange = {
|
||||
export const createPositionChange = ({ node, diff, dragging, nodeExtent }: CreatePositionChangeParams): NodePositionChange => {
|
||||
const change: NodePositionChange = {
|
||||
id: node.id,
|
||||
type: 'dimensions',
|
||||
type: 'position',
|
||||
dragging: !!dragging,
|
||||
handleBounds: node.handleBounds,
|
||||
}
|
||||
|
||||
if (diff) {
|
||||
@@ -99,5 +147,5 @@ export const getSelectionChanges = (items: FlowElements, selectedIds: string[])
|
||||
}
|
||||
|
||||
return res
|
||||
}, [] as SelectionChange[])
|
||||
}, [] as (NodeSelectionChange | EdgeSelectionChange)[])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user