update: Add slots to CustomConnectionLine.vue, Edge.vue, Node.vue

This commit is contained in:
Braks
2021-10-20 22:39:54 +02:00
parent b3d276bfb8
commit 9d9a90e9a7
25 changed files with 363 additions and 391 deletions
+1 -2
View File
@@ -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'
-31
View File
@@ -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))
}
+171
View File
@@ -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)
}
}
-23
View File
@@ -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
}
+4 -5
View File
@@ -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
+1 -21
View File
@@ -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',
+2 -7
View File
@@ -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),
}
}