update: Pass selection key code to zoom pane
* Use keypress handler in zoom pane instead of passing value as prop
This commit is contained in:
@@ -126,7 +126,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<ZoomPane
|
<ZoomPane
|
||||||
selectionKeyPressed={keyPressed.value}
|
|
||||||
elementsSelectable={props.elementsSelectable}
|
elementsSelectable={props.elementsSelectable}
|
||||||
zoomOnScroll={props.zoomOnScroll}
|
zoomOnScroll={props.zoomOnScroll}
|
||||||
zoomOnPinch={props.zoomOnPinch}
|
zoomOnPinch={props.zoomOnPinch}
|
||||||
@@ -139,6 +138,7 @@ export default defineComponent({
|
|||||||
defaultZoom={props.defaultZoom}
|
defaultZoom={props.defaultZoom}
|
||||||
translateExtent={props.translateExtent}
|
translateExtent={props.translateExtent}
|
||||||
zoomActivationKeyCode={props.zoomActivationKeyCode}
|
zoomActivationKeyCode={props.zoomActivationKeyCode}
|
||||||
|
selectionKeyCode={props.selectionKeyCode}
|
||||||
>
|
>
|
||||||
{slots.default ? slots.default() : ''}
|
{slots.default ? slots.default() : ''}
|
||||||
{keyPressed.value ? <UserSelection /> : ''}
|
{keyPressed.value ? <UserSelection /> : ''}
|
||||||
|
|||||||
+142
-136
@@ -2,7 +2,7 @@ import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom';
|
|||||||
import { select, pointer } from 'd3-selection';
|
import { select, pointer } from 'd3-selection';
|
||||||
import { clamp } from '../../utils';
|
import { clamp } from '../../utils';
|
||||||
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types';
|
import { FlowTransform, TranslateExtent, PanOnScrollMode, KeyCode, RevueFlowStore } from '../../types';
|
||||||
import { computed, defineComponent, inject, PropType, ref } from 'vue';
|
import { computed, defineComponent, inject, onUpdated, PropType, ref } from 'vue';
|
||||||
import useKeyPress from '../../hooks/useKeyPress';
|
import useKeyPress from '../../hooks/useKeyPress';
|
||||||
import useResizeHandler from '../../hooks/useResizeHandler';
|
import useResizeHandler from '../../hooks/useResizeHandler';
|
||||||
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
import { RevueFlowHooks } from '../../hooks/RevueFlowHooks';
|
||||||
@@ -23,6 +23,7 @@ interface ZoomPaneProps {
|
|||||||
defaultZoom?: number;
|
defaultZoom?: number;
|
||||||
translateExtent?: TranslateExtent;
|
translateExtent?: TranslateExtent;
|
||||||
zoomActivationKeyCode?: KeyCode;
|
zoomActivationKeyCode?: KeyCode;
|
||||||
|
selectionKeyCode?: KeyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -99,7 +100,6 @@ export default defineComponent({
|
|||||||
const hooks = inject<RevueFlowHooks>('hooks')!;
|
const hooks = inject<RevueFlowHooks>('hooks')!;
|
||||||
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null);
|
const zoomPane = templateRef<HTMLDivElement>('zoom-pane', null);
|
||||||
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 });
|
const prevTransform = ref<FlowTransform>({ x: 0, y: 0, zoom: 0 });
|
||||||
const zoomActivationKeyPressed = useKeyPress(props.zoomActivationKeyCode);
|
|
||||||
|
|
||||||
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean =>
|
||||||
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k;
|
prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k;
|
||||||
@@ -110,158 +110,164 @@ export default defineComponent({
|
|||||||
zoom: eventTransform.k
|
zoom: eventTransform.k
|
||||||
});
|
});
|
||||||
|
|
||||||
const { onReady } = useResizeHandler(zoomPane, store.updateSize);
|
const init = () => {
|
||||||
onReady(() => {
|
const { onReady } = useResizeHandler(zoomPane, store.updateSize);
|
||||||
if (props.defaultPosition && props.defaultZoom) {
|
onReady(() => {
|
||||||
const currentTranslateExtent =
|
if (props.defaultPosition && props.defaultZoom) {
|
||||||
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent;
|
const currentTranslateExtent =
|
||||||
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent);
|
typeof props.translateExtent !== 'undefined' ? props.translateExtent : store.translateExtent;
|
||||||
const selection = select(zoomPane.value as Element).call(d3ZoomInstance);
|
const d3ZoomInstance = zoom().scaleExtent([store.minZoom, store.maxZoom]).translateExtent(currentTranslateExtent);
|
||||||
|
const selection = select(zoomPane.value as Element).call(d3ZoomInstance);
|
||||||
|
|
||||||
const clampedX = clamp(props.defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0]);
|
const clampedX = clamp(props.defaultPosition[0], currentTranslateExtent[0][0], currentTranslateExtent[1][0]);
|
||||||
const clampedY = clamp(props.defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1]);
|
const clampedY = clamp(props.defaultPosition[1], currentTranslateExtent[0][1], currentTranslateExtent[1][1]);
|
||||||
const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom);
|
const clampedZoom = clamp(props.defaultZoom, store.minZoom, store.maxZoom);
|
||||||
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom);
|
const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom);
|
||||||
|
|
||||||
d3ZoomInstance.transform(selection, updatedTransform);
|
d3ZoomInstance.transform(selection, updatedTransform);
|
||||||
|
|
||||||
store.initD3Zoom({
|
store.initD3Zoom({
|
||||||
d3Zoom: d3ZoomInstance,
|
d3Zoom: d3ZoomInstance,
|
||||||
d3Selection: selection,
|
d3Selection: selection,
|
||||||
d3ZoomHandler: selection.on('wheel.zoom'),
|
d3ZoomHandler: selection.on('wheel.zoom'),
|
||||||
// we need to pass transform because zoom handler is not registered when we set the initial transform
|
// we need to pass transform because zoom handler is not registered when we set the initial transform
|
||||||
transform: [clampedX, clampedY, clampedZoom]
|
transform: [clampedX, clampedY, clampedZoom]
|
||||||
|
});
|
||||||
|
applyZoomHandlers(d3ZoomInstance);
|
||||||
|
applyZoomFilter(d3ZoomInstance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const applyZoomHandlers = (d3Zoom: ZoomBehavior<Element, unknown>) => {
|
||||||
|
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
|
if (viewChanged(prevTransform.value, event.transform)) {
|
||||||
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
|
prevTransform.value = flowTransform;
|
||||||
|
|
||||||
|
hooks.moveStart.trigger(flowTransform);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
applyZoomHandlers(d3ZoomInstance);
|
|
||||||
applyZoomFilter(d3ZoomInstance);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
whenever(zoomActivationKeyPressed, () => {
|
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
if (props.panOnScroll) {
|
if (viewChanged(prevTransform.value, event.transform)) {
|
||||||
store.d3Selection
|
const flowTransform = eventToFlowTransform(event.transform);
|
||||||
?.on('wheel', (event: WheelEvent) => {
|
prevTransform.value = flowTransform;
|
||||||
event.preventDefault();
|
|
||||||
event.stopImmediatePropagation();
|
|
||||||
|
|
||||||
const currentZoom = store.d3Selection?.property('__zoom').k || 1;
|
hooks.moveEnd.trigger(flowTransform);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (event.ctrlKey && props.zoomOnPinch) {
|
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
||||||
const point = pointer(event);
|
store.transform = [event.transform.x, event.transform.y, event.transform.k];
|
||||||
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
hooks.move.trigger(eventToFlowTransform(event.transform));
|
||||||
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10;
|
});
|
||||||
const zoom = currentZoom * Math.pow(2, pinchDelta);
|
|
||||||
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point);
|
|
||||||
|
|
||||||
return;
|
const selectionKeyPressed = computed(() => props.selectionKeyPressed);
|
||||||
}
|
whenever(selectionKeyPressed, () => {
|
||||||
|
if (selectionKeyPressed.value) {
|
||||||
|
d3Zoom.on('zoom', null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// increase scroll speed in firefox
|
const zoomActivationKeyPressed = useKeyPress(props.zoomActivationKeyCode);
|
||||||
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
whenever(zoomActivationKeyPressed, () => {
|
||||||
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
if (props.panOnScroll) {
|
||||||
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
store.d3Selection
|
||||||
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
?.on('wheel', (event: WheelEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
|
|
||||||
if (store.d3Selection && props.panOnScrollSpeed)
|
const currentZoom = store.d3Selection?.property('__zoom').k || 1;
|
||||||
store.d3Zoom?.translateBy(
|
|
||||||
store.d3Selection,
|
|
||||||
-(deltaX / currentZoom) * props.panOnScrollSpeed,
|
|
||||||
-(deltaY / currentZoom) * props.panOnScrollSpeed
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.on('wheel.zoom', null);
|
|
||||||
} else if (typeof store.d3ZoomHandler !== 'undefined') {
|
|
||||||
store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const applyZoomHandlers = (d3Zoom: ZoomBehavior<Element, unknown>) => {
|
if (event.ctrlKey && props.zoomOnPinch) {
|
||||||
d3Zoom.on('start', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
const point = pointer(event);
|
||||||
if (viewChanged(prevTransform.value, event.transform)) {
|
// taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10;
|
||||||
prevTransform.value = flowTransform;
|
const zoom = currentZoom * Math.pow(2, pinchDelta);
|
||||||
|
if (store.d3Selection) store.d3Zoom?.scaleTo(store.d3Selection, zoom, point);
|
||||||
|
|
||||||
hooks.moveStart.trigger(flowTransform);
|
return;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
d3Zoom.on('end', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
// increase scroll speed in firefox
|
||||||
if (viewChanged(prevTransform.value, event.transform)) {
|
// firefox: deltaMode === 1; chrome: deltaMode === 0
|
||||||
const flowTransform = eventToFlowTransform(event.transform);
|
const deltaNormalize = event.deltaMode === 1 ? 20 : 1;
|
||||||
prevTransform.value = flowTransform;
|
const deltaX = props.panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize;
|
||||||
|
const deltaY = props.panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize;
|
||||||
|
|
||||||
hooks.moveEnd.trigger(flowTransform);
|
if (store.d3Selection && props.panOnScrollSpeed)
|
||||||
}
|
store.d3Zoom?.translateBy(
|
||||||
});
|
store.d3Selection,
|
||||||
|
-(deltaX / currentZoom) * props.panOnScrollSpeed,
|
||||||
|
-(deltaY / currentZoom) * props.panOnScrollSpeed
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.on('wheel.zoom', null);
|
||||||
|
} else if (typeof store.d3ZoomHandler !== 'undefined') {
|
||||||
|
store.d3Selection?.on('wheel', null).on('wheel.zoom', store.d3ZoomHandler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
d3Zoom.on('zoom', (event: D3ZoomEvent<HTMLDivElement, any>) => {
|
const applyZoomFilter = (d3Zoom: ZoomBehavior<Element, unknown>) => {
|
||||||
store.transform = [event.transform.x, event.transform.y, event.transform.k];
|
d3Zoom.filter((event: MouseEvent) => {
|
||||||
hooks.move.trigger(eventToFlowTransform(event.transform));
|
const zoomScroll = props.zoomOnScroll;
|
||||||
});
|
const pinchZoom = props.zoomOnPinch && event.ctrlKey;
|
||||||
|
|
||||||
const selectionKeyPressed = computed(() => props.selectionKeyPressed);
|
// if all interactions are disabled, we prevent all zoom events
|
||||||
whenever(selectionKeyPressed, () => {
|
if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) {
|
||||||
if (selectionKeyPressed.value) {
|
return false;
|
||||||
d3Zoom.on('zoom', null);
|
}
|
||||||
}
|
|
||||||
});
|
// during a selection we prevent all other interactions
|
||||||
|
if (props.selectionKeyPressed) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if zoom on double click is disabled, we prevent the double click event
|
||||||
|
if (!props.zoomOnDoubleClick && event.type === 'dblclick') {
|
||||||
|
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('.revue-flow__node') || (event.target as Element).closest('.revue-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('.revue-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;
|
||||||
|
});
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const applyZoomFilter = (d3Zoom: ZoomBehavior<Element, unknown>) => {
|
init();
|
||||||
d3Zoom.filter((event: MouseEvent) => {
|
onUpdated(() => init());
|
||||||
const zoomScroll = props.zoomOnScroll;
|
|
||||||
const pinchZoom = props.zoomOnPinch && event.ctrlKey;
|
|
||||||
|
|
||||||
// if all interactions are disabled, we prevent all zoom events
|
|
||||||
if (!props.paneMoveable && !zoomScroll && !props.panOnScroll && !props.zoomOnDoubleClick && !props.zoomOnPinch) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// during a selection we prevent all other interactions
|
|
||||||
if (props.selectionKeyPressed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if zoom on double click is disabled, we prevent the double click event
|
|
||||||
if (!props.zoomOnDoubleClick && event.type === 'dblclick') {
|
|
||||||
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('.revue-flow__node') || (event.target as Element).closest('.revue-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('.revue-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;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div ref="zoom-pane" class="revue-flow__renderer revue-flow__zoompane">
|
<div ref="zoom-pane" class="revue-flow__renderer revue-flow__zoompane">
|
||||||
|
|||||||
Reference in New Issue
Block a user