diff --git a/docs/pages/examples/custom-node.vue b/docs/pages/examples/custom-node.vue index 4b49994e..5b9f1076 100644 --- a/docs/pages/examples/custom-node.vue +++ b/docs/pages/examples/custom-node.vue @@ -43,60 +43,58 @@ const nodeColor = (n: Node): string => { return '#fff' } -onMounted(() => { - const onChange = (event: Event) => { - elements.value = elements.value.map((e: FlowElement) => { - if (isEdge(e) || e.id !== '2') { - return e - } - const color = (event.target as HTMLInputElement).value - bgColor.value = color +const onChange = (event: Event) => { + elements.value = elements.value.map((e: FlowElement) => { + if (isEdge(e) || e.id !== '2') { + return e + } + const color = (event.target as HTMLInputElement).value + bgColor.value = color - return { - ...e, - data: { - ...e.data, - color, - }, - } - }) - } + return { + ...e, + data: { + ...e.data, + color, + }, + } + }) +} - elements.value = [ - { - id: '1', - type: 'input', - data: { label: 'An input node' }, - position: { x: 0, y: 50 }, - sourcePosition: Position.Right, - }, - { - id: '2', - type: 'selectorNode', - data: { onChange, color: bgColor.value }, - style: { border: '1px solid #777', padding: '10px' }, - position: { x: 250, y: 50 }, - }, - { - id: '3', - type: 'output', - data: { label: 'Output A' }, - position: { x: 650, y: 25 }, - targetPosition: Position.Left, - }, - { - id: '4', - type: 'output', - data: { label: 'Output B' }, - position: { x: 650, y: 100 }, - targetPosition: Position.Left, - }, +elements.value = [ + { + id: '1', + type: 'input', + data: { label: 'An input node' }, + position: { x: 0, y: 50 }, + sourcePosition: Position.Right, + }, + { + id: '2', + type: 'selectorNode', + data: { onChange, color: bgColor.value }, + style: { border: '1px solid #777', padding: '10px' }, + position: { x: 250, y: 50 }, + }, + { + id: '3', + type: 'output', + data: { label: 'Output A' }, + position: { x: 650, y: 25 }, + targetPosition: Position.Left, + }, + { + id: '4', + type: 'output', + data: { label: 'Output B' }, + position: { x: 650, y: 100 }, + targetPosition: Position.Left, + }, - { id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } }, - { id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } }, - { id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }, - ] -}) + { id: 'e1-2', source: '1', target: '2', animated: true, style: { stroke: '#fff' } }, + { id: 'e2a-3', source: '2', sourceHandle: 'a', target: '3', animated: true, style: { stroke: '#fff' } }, + { id: 'e2b-4', source: '2', sourceHandle: 'b', target: '4', animated: true, style: { stroke: '#fff' } }, +] const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value)) diff --git a/docs/pages/examples/layouting.vue b/docs/pages/examples/layouting.vue index bc92dba2..9678d51a 100644 --- a/docs/pages/examples/layouting.vue +++ b/docs/pages/examples/layouting.vue @@ -27,7 +27,6 @@ const elements = ref(initialElements) const onConnect = (params: Connection | Edge) => (elements.value = addEdge({ ...params, animated: true }, elements.value)) const onElementsRemove = (elementsToRemove: Elements) => (elements.value = removeElements(elementsToRemove, elements.value)) -// todo changing elements not properly updating flowchart... const onLayout = (direction: string) => { const isHorizontal = direction === 'LR' dagreGraph.setGraph({ rankdir: direction }) diff --git a/src/composables/useZoomPanHelper.ts b/src/composables/useZoomPanHelper.ts index 0fbf396b..e317318e 100644 --- a/src/composables/useZoomPanHelper.ts +++ b/src/composables/useZoomPanHelper.ts @@ -23,9 +23,9 @@ export default (store = useStore()): UseZoomPanHelper => { bounds, store.dimensions.width, store.dimensions.height, - options.minZoom || store.minZoom, - options.maxZoom || store.maxZoom, - options.padding || DEFAULT_PADDING, + options.minZoom ?? store.minZoom, + options.maxZoom ?? store.maxZoom, + options.padding ?? DEFAULT_PADDING, ) const transform = zoomIdentity.translate(x, y).scale(zoom) diff --git a/src/container/ZoomPane/ZoomPane.vue b/src/container/ZoomPane/ZoomPane.vue index ca136c37..d415976c 100644 --- a/src/container/ZoomPane/ZoomPane.vue +++ b/src/container/ZoomPane/ZoomPane.vue @@ -69,122 +69,114 @@ until(zoomPaneEl) store.initD3Zoom({ d3Zoom: d3z, d3Selection: d3s, d3ZoomHandler }) store.transform = [updatedTransform.x, updatedTransform.y, updatedTransform.k] - const applyZoomHandlers = () => { - d3z.on('start', (event: D3ZoomEvent) => { - if (viewChanged(transform.value, event.transform)) { - const flowTransform = eventToFlowTransform(event.transform) - transform.value = flowTransform + d3z.on('start', (event: D3ZoomEvent) => { + if (viewChanged(transform.value, event.transform)) { + const flowTransform = eventToFlowTransform(event.transform) + transform.value = flowTransform - hooks.moveStart.trigger(flowTransform) - } - }) + hooks.moveStart.trigger(flowTransform) + } + }) - d3z.on('end', (event: D3ZoomEvent) => { - if (viewChanged(transform.value, event.transform)) { - const flowTransform = eventToFlowTransform(event.transform) - transform.value = flowTransform + d3z.on('end', (event: D3ZoomEvent) => { + if (viewChanged(transform.value, event.transform)) { + const flowTransform = eventToFlowTransform(event.transform) + transform.value = flowTransform - hooks.moveEnd.trigger(flowTransform) - } - }) + hooks.moveEnd.trigger(flowTransform) + } + }) - useKeyPress(props.selectionKeyCode, (keyPress) => { - if (keyPress) { - d3z.on('zoom', null) - } else { - d3z.on('zoom', (event: D3ZoomEvent) => { - if (viewChanged(transform.value, event.transform)) { - const flowTransform = eventToFlowTransform(event.transform) - transform.value = flowTransform + useKeyPress(props.selectionKeyCode, (keyPress) => { + if (keyPress) { + d3z.on('zoom', null) + } else { + d3z.on('zoom', (event: D3ZoomEvent) => { + if (viewChanged(transform.value, event.transform)) { + const flowTransform = eventToFlowTransform(event.transform) + transform.value = flowTransform - hooks.move.trigger(flowTransform) + hooks.move.trigger(flowTransform) + } + }) + } + }) + + useKeyPress(props.zoomActivationKeyCode, (keyPress) => { + if (props.panOnScroll && keyPress) { + d3s + ?.on('wheel', (event: WheelEvent) => { + event.preventDefault() + event.stopImmediatePropagation() + + const currentZoom = d3s?.property('__zoom').k || 1 + + if (event.ctrlKey && props.zoomOnPinch) { + const point = pointer(event) + // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js + const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10 + const zoom = currentZoom * Math.pow(2, pinchDelta) + if (d3s) d3z.scaleTo(d3s, zoom, point) + + return } + + // increase scroll speed in firefox + // firefox: deltaMode === 1; chrome: deltaMode === 0 + const deltaNormalize = event.deltaMode === 1 ? 20 : 1 + const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize + const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize + + if (d3s && props.panOnScrollSpeed) + d3z?.translateBy( + d3s, + -(deltaX / currentZoom) * props.panOnScrollSpeed, + -(deltaY / currentZoom) * props.panOnScrollSpeed, + ) }) - } - }) + .on('wheel.zoom', null) + } else if (typeof d3ZoomHandler !== 'undefined') { + d3s?.on('wheel', null).on('wheel.zoom', d3ZoomHandler) + } + }) - useKeyPress(props.zoomActivationKeyCode, (keyPress) => { - if (props.panOnScroll && keyPress) { - d3s - ?.on('wheel', (event: WheelEvent) => { - event.preventDefault() - event.stopImmediatePropagation() + const keyPress = useKeyPress(props.selectionKeyCode) + d3z.filter((event: MouseEvent) => { + const zoomScroll = props.zoomOnScroll + const pinchZoom = props.zoomOnPinch && event.ctrlKey - const currentZoom = d3s?.property('__zoom').k || 1 + // if all interactions are disabled, we prevent all zoom events + if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) return false - if (event.ctrlKey && props.zoomOnPinch) { - const point = pointer(event) - // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js - const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10 - const zoom = currentZoom * Math.pow(2, pinchDelta) - if (d3s) d3z.scaleTo(d3s, zoom, point) + // during a selection we prevent all other interactions + if (keyPress.value) return false - return - } + // if zoom on double click is disabled, we prevent the double click event + if (!props.zoomOnDoubleClick && event.type === 'dblclick') return false - // increase scroll speed in firefox - // firefox: deltaMode === 1; chrome: deltaMode === 0 - const deltaNormalize = event.deltaMode === 1 ? 20 : 1 - const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize - const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize + if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') return false - if (d3s && props.panOnScrollSpeed) - d3z?.translateBy( - d3s, - -(deltaX / currentZoom) * props.panOnScrollSpeed, - -(deltaY / currentZoom) * props.panOnScrollSpeed, - ) - }) - .on('wheel.zoom', null) - } else if (typeof d3ZoomHandler !== 'undefined') { - d3s?.on('wheel', null).on('wheel.zoom', d3ZoomHandler) - } - }) - } + // when the target element is a node, we still allow zooming + if ( + ((event.target as Element).closest('.vue-flow__node') || (event.target as Element).closest('.vue-flow__edge')) && + event.type !== 'wheel' + ) + return false - const applyZoomFilter = () => { - const keyPress = useKeyPress(props.selectionKeyCode) - d3z.filter((event: MouseEvent) => { - const zoomScroll = props.zoomOnScroll - const pinchZoom = props.zoomOnPinch && event.ctrlKey + // when the target element is a node selection, we still allow zooming + if ((event.target as Element).closest('.vue-flow__nodesselection') && event.type !== 'wheel') return false - // if all interactions are disabled, we prevent all zoom events - if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) - return false + if (!props.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false - // during a selection we prevent all other interactions - if (keyPress.value) return false + // when there is no scroll handling enabled, we prevent all wheel events + if (!zoomScroll && !props.panOnScroll && !pinchZoom && event.type === 'wheel') return false - // if zoom on double click is disabled, we prevent the double click event - if (!props.zoomOnDoubleClick && event.type === 'dblclick') return false + // if the pane is not movable, we prevent dragging it with mousestart or touchstart + if (!props.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) return false - if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') return false - - // when the target element is a node, we still allow zooming - if ( - ((event.target as Element).closest('.vue-flow__node') || (event.target as Element).closest('.vue-flow__edge')) && - event.type !== 'wheel' - ) - return false - - // when the target element is a node selection, we still allow zooming - if ((event.target as Element).closest('.vue-flow__nodesselection') && event.type !== 'wheel') return false - - if (!props.zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false - - // when there is no scroll handling enabled, we prevent all wheel events - if (!zoomScroll && !props.panOnScroll && !pinchZoom && event.type === 'wheel') return false - - // if the pane is not movable, we prevent dragging it with mousestart or touchstart - if (!props.paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) return false - - // default filter for d3-zoom - return (!event.ctrlKey || event.type === 'wheel') && !event.button - }) - } - - applyZoomHandlers() - applyZoomFilter() + // default filter for d3-zoom + return (!event.ctrlKey || event.type === 'wheel') && !event.button + }) }) const { width, height } = useElementBounding(zoomPaneEl) @@ -197,9 +189,8 @@ watch(width, (val) => (store.dimensions.width = val)) watch(height, (val) => (store.dimensions.height = val)) watch(transform, (val) => (store.transform = [val.x, val.y, val.zoom])) +const { zoomIn, zoomOut, zoomTo, transform: setTransform, fitView } = useZoomPanHelper() onMounted(() => { - const { zoomIn, zoomOut, zoomTo, transform: setTransform, fitView } = useZoomPanHelper() - watchOnce( () => width.value && height.value, () => { diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 4978db39..3926a3ac 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -222,7 +222,7 @@ export const getRectOfNodes = (nodes: Node[]): Rect => { ...position, width, height, - } as any), + }), ), { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }, )