update: Add slots to CustomConnectionLine.vue, Edge.vue, Node.vue
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
export { default as useGlobalKeyHandler } from './useGlobalKeyHandler'
|
||||
export { default as useHandle } from './useHandle'
|
||||
export { default as useHooks } from './useHooks'
|
||||
export { default as useKeyPress } from './useKeyPress'
|
||||
export { default as useResizeHandler } from './useResizeHandler'
|
||||
export { default as useUpdateNodeInternals } from './useUpdateNodeInternals'
|
||||
export { default as useZoom } from './useZoom'
|
||||
export { default as useZoomPanHelper } from './useZoomPanHelper'
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import useKeyPress from './useKeyPress'
|
||||
import { isNode, getConnectedEdges } from '~/utils/graph'
|
||||
import { Elements, KeyCode, ElementId, FlowElement } from '~/types'
|
||||
import { Store } from '~/context'
|
||||
|
||||
interface HookParams {
|
||||
deleteKeyCode: KeyCode
|
||||
multiSelectionKeyCode: KeyCode
|
||||
onElementsRemove?: (elements: Elements) => void
|
||||
}
|
||||
|
||||
export default ({ deleteKeyCode, multiSelectionKeyCode, onElementsRemove = () => {} }: HookParams): void => {
|
||||
const store = inject(Store)!
|
||||
|
||||
useKeyPress(deleteKeyCode, (keyPressed) => {
|
||||
if (keyPressed && store.selectedElements) {
|
||||
const selectedNodes = store.selectedElements.filter(isNode)
|
||||
const connectedEdges = getConnectedEdges(selectedNodes, store.edges)
|
||||
const elementsToRemove = [...store.selectedElements, ...connectedEdges].reduce(
|
||||
(res, item) => res.set(item.id, item),
|
||||
new Map<ElementId, FlowElement>(),
|
||||
)
|
||||
|
||||
onElementsRemove(Array.from(elementsToRemove.values()))
|
||||
store.unsetNodesSelection()
|
||||
store.resetSelectedElements()
|
||||
}
|
||||
})
|
||||
|
||||
useKeyPress(multiSelectionKeyCode, (keyPressed) => (store.multiSelectionActive = keyPressed))
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
import { getHostForElement } from '~/utils'
|
||||
import { Hooks, Store } from '~/context'
|
||||
import { Connection, ConnectionMode, ElementId, HandleType, ValidConnectionFunc } from '~/types'
|
||||
|
||||
type Result = {
|
||||
elementBelow: Element | null
|
||||
isValid: boolean
|
||||
connection: Connection
|
||||
isHoveringHandle: boolean
|
||||
}
|
||||
|
||||
// checks if element below mouse is a handle and returns connection in form of an object { source: 123, target: 312 }
|
||||
export function checkElementBelowIsValid(
|
||||
event: MouseEvent,
|
||||
connectionMode: ConnectionMode,
|
||||
isTarget: boolean,
|
||||
nodeId: ElementId,
|
||||
handleId: ElementId | null,
|
||||
isValidConnection: ValidConnectionFunc,
|
||||
doc: Document,
|
||||
) {
|
||||
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY)
|
||||
const elementBelowIsTarget = elementBelow?.classList.contains('target') || false
|
||||
const elementBelowIsSource = elementBelow?.classList.contains('source') || false
|
||||
|
||||
const result: Result = {
|
||||
elementBelow,
|
||||
isValid: false,
|
||||
connection: { source: null, target: null, sourceHandle: null, targetHandle: null },
|
||||
isHoveringHandle: false,
|
||||
}
|
||||
|
||||
if (elementBelow && (elementBelowIsTarget || elementBelowIsSource)) {
|
||||
result.isHoveringHandle = true
|
||||
|
||||
// in strict mode we don't allow target to target or source to source connections
|
||||
const isValid =
|
||||
connectionMode === ConnectionMode.Strict ? (isTarget && elementBelowIsSource) || (!isTarget && elementBelowIsTarget) : true
|
||||
|
||||
if (isValid) {
|
||||
const elementBelowNodeId = elementBelow.getAttribute('data-nodeid')
|
||||
const elementBelowHandleId = elementBelow.getAttribute('data-handleid')
|
||||
const connection: Connection = isTarget
|
||||
? {
|
||||
source: elementBelowNodeId,
|
||||
sourceHandle: elementBelowHandleId,
|
||||
target: nodeId,
|
||||
targetHandle: handleId,
|
||||
}
|
||||
: {
|
||||
source: nodeId,
|
||||
sourceHandle: handleId,
|
||||
target: elementBelowNodeId,
|
||||
targetHandle: elementBelowHandleId,
|
||||
}
|
||||
|
||||
result.connection = connection
|
||||
result.isValid = isValidConnection(connection)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
function resetRecentHandle(hoveredHandle: Element): void {
|
||||
hoveredHandle?.classList.remove('revue-flow__handle-valid')
|
||||
hoveredHandle?.classList.remove('revue-flow__handle-connecting')
|
||||
}
|
||||
|
||||
export default function () {
|
||||
const store = inject(Store)!
|
||||
const hooks = inject(Hooks)!
|
||||
|
||||
return (
|
||||
event: MouseEvent,
|
||||
handleId: ElementId,
|
||||
nodeId: ElementId,
|
||||
isTarget: boolean,
|
||||
isValidConnection: ValidConnectionFunc = () => {
|
||||
return true
|
||||
},
|
||||
elementEdgeUpdaterType?: HandleType,
|
||||
) => {
|
||||
const revueFlowNode = (event.target as Element).closest('.revue-flow')
|
||||
// when revue-flow is used inside a shadow root we can't use document
|
||||
const doc = getHostForElement(event.target as HTMLElement)
|
||||
|
||||
if (!doc) return
|
||||
|
||||
const elementBelow = doc.elementFromPoint(event.clientX, event.clientY)
|
||||
const elementBelowIsTarget = elementBelow?.classList.contains('target')
|
||||
const elementBelowIsSource = elementBelow?.classList.contains('source')
|
||||
|
||||
if (!revueFlowNode || (!elementBelowIsTarget && !elementBelowIsSource && !elementEdgeUpdaterType)) return
|
||||
|
||||
const handleType = elementEdgeUpdaterType || (elementBelowIsTarget ? 'target' : 'source')
|
||||
const containerBounds = revueFlowNode.getBoundingClientRect()
|
||||
let recentHoveredHandle: Element
|
||||
|
||||
store.connectionPosition.x = event.clientX - containerBounds.left
|
||||
store.connectionPosition.y = event.clientY - containerBounds.top
|
||||
|
||||
store.setConnectionNodeId({
|
||||
connectionNodeId: nodeId,
|
||||
connectionHandleId: handleId,
|
||||
connectionHandleType: handleType,
|
||||
})
|
||||
hooks.connectStart.trigger({ event, params: { nodeId, handleId, handleType } })
|
||||
|
||||
function onMouseMove(event: MouseEvent) {
|
||||
store.connectionPosition.x = event.clientX - containerBounds.left
|
||||
store.connectionPosition.y = event.clientY - containerBounds.top
|
||||
|
||||
const { connection, elementBelow, isValid, isHoveringHandle } = checkElementBelowIsValid(
|
||||
event,
|
||||
store.connectionMode,
|
||||
isTarget,
|
||||
nodeId,
|
||||
handleId,
|
||||
isValidConnection,
|
||||
doc,
|
||||
)
|
||||
|
||||
if (!isHoveringHandle) {
|
||||
return resetRecentHandle(recentHoveredHandle)
|
||||
}
|
||||
|
||||
const isOwnHandle = connection.source === connection.target
|
||||
|
||||
if (!isOwnHandle && elementBelow) {
|
||||
recentHoveredHandle = elementBelow
|
||||
elementBelow.classList.add('revue-flow__handle-connecting')
|
||||
elementBelow.classList.toggle('revue-flow__handle-valid', isValid)
|
||||
}
|
||||
}
|
||||
|
||||
function onMouseUp(event: MouseEvent) {
|
||||
const { connection, isValid } = checkElementBelowIsValid(
|
||||
event,
|
||||
store.connectionMode,
|
||||
isTarget,
|
||||
nodeId,
|
||||
handleId,
|
||||
isValidConnection,
|
||||
doc,
|
||||
)
|
||||
|
||||
hooks.connectStop.trigger(event)
|
||||
|
||||
if (isValid) {
|
||||
hooks.connect.trigger(connection)
|
||||
}
|
||||
|
||||
hooks.connectEnd.trigger(event)
|
||||
|
||||
if (elementEdgeUpdaterType) {
|
||||
hooks.edgeUpdateEnd.trigger({ event } as any)
|
||||
}
|
||||
|
||||
resetRecentHandle(recentHoveredHandle)
|
||||
store.setConnectionNodeId({ connectionNodeId: undefined, connectionHandleId: undefined, connectionHandleType: undefined })
|
||||
store.connectionPosition = { x: NaN, y: NaN }
|
||||
|
||||
doc.removeEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject)
|
||||
doc.removeEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject)
|
||||
}
|
||||
|
||||
doc.addEventListener('mousemove', onMouseMove as EventListenerOrEventListenerObject)
|
||||
doc.addEventListener('mouseup', onMouseUp as EventListenerOrEventListenerObject)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Ref } from 'vue'
|
||||
import { getDimensions } from '~/utils'
|
||||
import { Dimensions } from '~/types'
|
||||
|
||||
export default function (el: Ref<HTMLDivElement>) {
|
||||
const dimensions = ref<Dimensions>({ width: 0, height: 0 })
|
||||
const updateDimensions = () => {
|
||||
const unrefEl = unrefElement(el)
|
||||
if (!unrefEl) return
|
||||
|
||||
const size = getDimensions(unrefEl as HTMLDivElement)
|
||||
if (size.height === 0 || size.width === 0)
|
||||
console.log('The revue Flow parent container needs a width and a height to render the graph.')
|
||||
else dimensions.value = size
|
||||
}
|
||||
|
||||
useEventListener(window, 'resize', updateDimensions)
|
||||
useResizeObserver(el, () => updateDimensions())
|
||||
|
||||
until(el).toBeTruthy().then(updateDimensions)
|
||||
|
||||
return dimensions
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ElementId, RevueFlowStore, UpdateNodeInternals } from '../types'
|
||||
import { ElementId, UpdateNodeInternals } from '~/types'
|
||||
import { Store } from '~/context'
|
||||
|
||||
function useUpdateNodeInternals(): UpdateNodeInternals {
|
||||
const store = inject<RevueFlowStore>('store')!
|
||||
export default function (): UpdateNodeInternals {
|
||||
const store = inject(Store)!
|
||||
|
||||
return (id: ElementId) => {
|
||||
const nodeElement: HTMLDivElement | null = document.querySelector(`.revue-flow__node[data-id="${id}"]`)
|
||||
@@ -11,5 +12,3 @@ function useUpdateNodeInternals(): UpdateNodeInternals {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default useUpdateNodeInternals
|
||||
|
||||
@@ -2,7 +2,7 @@ import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom'
|
||||
import { pointer, select } from 'd3-selection'
|
||||
import { Ref } from 'vue'
|
||||
import { get } from '@vueuse/core'
|
||||
import { D3Selection, D3Zoom, FlowTransform, KeyCode, PanOnScrollMode, Transform } from '~/types'
|
||||
import { FlowTransform, PanOnScrollMode, Transform, UseZoom, UseZoomOptions } from '~/types'
|
||||
import { clamp } from '~/utils'
|
||||
import useKeyPress from '~/composables/useKeyPress'
|
||||
import { Hooks, Store } from '~/context'
|
||||
@@ -16,26 +16,6 @@ const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => (
|
||||
zoom: eventTransform.k,
|
||||
})
|
||||
|
||||
interface UseZoomOptions {
|
||||
selectionKeyCode?: KeyCode
|
||||
zoomActivationKeyCode?: KeyCode
|
||||
paneMoveable?: boolean
|
||||
defaultZoom?: number
|
||||
defaultPosition?: [number, number]
|
||||
zoomOnScroll?: boolean
|
||||
zoomOnPinch?: boolean
|
||||
panOnScroll?: boolean
|
||||
panOnScrollSpeed?: number
|
||||
panOnScrollMode?: PanOnScrollMode
|
||||
zoomOnDoubleClick?: boolean
|
||||
}
|
||||
|
||||
interface UseZoom {
|
||||
transform: Ref<Transform>
|
||||
d3Zoom: Ref<D3Zoom>
|
||||
d3Selection: Ref<D3Selection>
|
||||
}
|
||||
|
||||
export default function (el: Ref<HTMLDivElement>, options: UseZoomOptions): UseZoom {
|
||||
const {
|
||||
selectionKeyCode = 'Shift',
|
||||
|
||||
@@ -13,13 +13,10 @@ export default function (): UseZoomPanHelper {
|
||||
zoomTo: (zoomLevel: number) => store.d3Selection && store.d3Zoom?.scaleTo(store.d3Selection, zoomLevel),
|
||||
transform: (transform: FlowTransform) => {
|
||||
const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom)
|
||||
|
||||
store.d3Selection && store.d3Zoom?.transform(store.d3Selection, nextTransform)
|
||||
},
|
||||
fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => {
|
||||
if (!store.nodes.length) {
|
||||
return
|
||||
}
|
||||
if (!store.nodes.length) return
|
||||
|
||||
const bounds = getRectOfNodes(options.includeHiddenNodes ? store.nodes : store.nodes.filter((node) => !node.isHidden))
|
||||
const [x, y, zoom] = getTransformForBounds(
|
||||
@@ -55,8 +52,6 @@ export default function (): UseZoomPanHelper {
|
||||
|
||||
store.d3Selection && store.d3Zoom?.transform(store.d3Selection, transform)
|
||||
},
|
||||
project: (position: XYPosition) => {
|
||||
return pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid)
|
||||
},
|
||||
project: (position: XYPosition) => pointToRendererPoint(position, store.transform, store.snapToGrid, store.snapGrid),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user