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:
Braks
2021-08-17 22:14:39 +02:00
parent d6a8ed6be5
commit 347f1edc32
2 changed files with 143 additions and 137 deletions
+1 -1
View File
@@ -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
View File
@@ -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">