diff --git a/examples/vite/index.css b/examples/vite/index.css index 69e2a6198..1c22f076e 100644 --- a/examples/vite/index.css +++ b/examples/vite/index.css @@ -9,7 +9,6 @@ body { color: #111; - padding: 5px; } #root { diff --git a/packages/background/CHANGELOG.md b/packages/background/CHANGELOG.md index 1169c3778..ad06050b1 100644 --- a/packages/background/CHANGELOG.md +++ b/packages/background/CHANGELOG.md @@ -1,5 +1,11 @@ # @vue-flow/background +## 1.3.2 + +### Patch Changes + +- [#1692](https://github.com/bcakmakoglu/vue-flow/pull/1692) [`f08c15d`](https://github.com/bcakmakoglu/vue-flow/commit/f08c15da511ab3e6e6960e2e80c32d2870d6e755) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Set default offset value to `0` + ## 1.3.1 ### Patch Changes diff --git a/packages/background/package.json b/packages/background/package.json index b2af8223d..b185f436a 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -1,6 +1,6 @@ { "name": "@vue-flow/background", - "version": "1.3.1", + "version": "1.3.2", "private": false, "license": "MIT", "author": "Burak Cakmakoglu<78412429+bcakmakoglu@users.noreply.github.com>", diff --git a/packages/background/src/Background.vue b/packages/background/src/Background.vue index dce1a78b4..ab7d0ca09 100644 --- a/packages/background/src/Background.vue +++ b/packages/background/src/Background.vue @@ -18,21 +18,19 @@ const { bgColor, patternColor: initialPatternColor, color: _patternColor, - offset = 2, + offset = 0, } = defineProps() const { id: vueFlowId, viewport } = useVueFlow() const background = computed(() => { + const zoom = viewport.value.zoom const [gapX, gapY] = Array.isArray(gap) ? gap : [gap, gap] - const scaledGap: [number, number] = [gapX * viewport.value.zoom || 1, gapY * viewport.value.zoom || 1] - const scaledSize = size * viewport.value.zoom + const scaledGap: [number, number] = [gapX * zoom || 1, gapY * zoom || 1] + const scaledSize = size * zoom const [offsetX, offsetY]: [number, number] = Array.isArray(offset) ? offset : [offset, offset] - const scaledOffset: [number, number] = [ - offsetX * viewport.value.zoom || 1 + scaledGap[0] / 2, - offsetY * viewport.value.zoom || 1 + scaledGap[1] / 2, - ] + const scaledOffset: [number, number] = [offsetX * zoom || 1 + scaledGap[0] / 2, offsetY * zoom || 1 + scaledGap[1] / 2] return { scaledGap, diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 70c54899b..0ca65ceb0 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,17 @@ # @vue-flow/core +## 1.41.5 + +### Patch Changes + +- [#1680](https://github.com/bcakmakoglu/vue-flow/pull/1680) [`b6c500d`](https://github.com/bcakmakoglu/vue-flow/commit/b6c500dca8a8baa054802a80772990c723f83e2d) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - check if event on drag end is mouse/touch event or a usedrag event + +- [#1696](https://github.com/bcakmakoglu/vue-flow/pull/1696) [`249efce`](https://github.com/bcakmakoglu/vue-flow/commit/249efce8107f1b51ecd98206953015b9b470e342) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Properly release key combinations when one of the keys is unpressed + +- [#1693](https://github.com/bcakmakoglu/vue-flow/pull/1693) [`f28ffba`](https://github.com/bcakmakoglu/vue-flow/commit/f28ffba4d3f8166f2e80a9e6805d17db14ab2a89) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Allow using the `+` key in key combinations + +- [#1695](https://github.com/bcakmakoglu/vue-flow/pull/1695) [`b09ad8e`](https://github.com/bcakmakoglu/vue-flow/commit/b09ad8ea35e974c83b5ad2ceea49e7971ff62cf3) Thanks [@bcakmakoglu](https://github.com/bcakmakoglu)! - Escape node labels and avoid rendering them as innerHTML + ## 1.41.4 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 1c2647b66..209e20dc1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@vue-flow/core", - "version": "1.41.4", + "version": "1.41.5", "private": false, "license": "MIT", "author": "Burak Cakmakoglu<78412429+bcakmakoglu@users.noreply.github.com>", diff --git a/packages/core/src/components/Nodes/DefaultNode.ts b/packages/core/src/components/Nodes/DefaultNode.ts index 839e7a7f7..e8af0cbda 100644 --- a/packages/core/src/components/Nodes/DefaultNode.ts +++ b/packages/core/src/components/Nodes/DefaultNode.ts @@ -1,5 +1,5 @@ import type { Component, FunctionalComponent } from 'vue' -import { h } from 'vue' +import { Fragment, h } from 'vue' import Handle from '../Handle/Handle.vue' import type { NodeProps } from '../../types' import { Position } from '../../types' @@ -17,7 +17,7 @@ const DefaultNode: FunctionalComponent> = function ({ return [ h(Handle as Component, { type: 'target', position: targetPosition, connectable, isValidConnection: isValidTargetPos }), - typeof label !== 'string' && label ? h(label) : h('div', { innerHTML: label }), + typeof label !== 'string' && label ? h(label) : h(Fragment, [label]), h(Handle as Component, { type: 'source', position: sourcePosition, connectable, isValidConnection: isValidSourcePos }), ] } diff --git a/packages/core/src/components/Nodes/InputNode.ts b/packages/core/src/components/Nodes/InputNode.ts index c963faaf0..bc8922650 100644 --- a/packages/core/src/components/Nodes/InputNode.ts +++ b/packages/core/src/components/Nodes/InputNode.ts @@ -1,5 +1,5 @@ import type { Component, FunctionalComponent } from 'vue' -import { h } from 'vue' +import { Fragment, h } from 'vue' import Handle from '../Handle/Handle.vue' import type { NodeProps } from '../../types' import { Position } from '../../types' @@ -14,7 +14,7 @@ const InputNode: FunctionalComponent> = function ({ const label = data.label || _label return [ - typeof label !== 'string' && label ? h(label) : h('div', { innerHTML: label }), + typeof label !== 'string' && label ? h(label) : h(Fragment, [label]), h(Handle as Component, { type: 'source', position: sourcePosition, connectable, isValidConnection: isValidSourcePos }), ] } diff --git a/packages/core/src/components/Nodes/NodeWrapper.ts b/packages/core/src/components/Nodes/NodeWrapper.ts index 9465525e5..d54d76914 100644 --- a/packages/core/src/components/Nodes/NodeWrapper.ts +++ b/packages/core/src/components/Nodes/NodeWrapper.ts @@ -22,10 +22,11 @@ import { elementSelectionKeys, getXYZPos, handleNodeClick, + snapPosition, } from '../../utils' import { NodeId, NodeRef, Slots } from '../../context' import { isInputDOMNode, useDrag, useNode, useNodeHooks, useUpdateNodePositions, useVueFlow } from '../../composables' -import type { NodeComponent } from '../../types' +import type { MouseTouchEvent, NodeComponent } from '../../types' interface Props { id: string @@ -321,14 +322,15 @@ const NodeWrapper = defineComponent({ } /** this re-calculates the current position, necessary for clamping by a node's extent */ function clampPosition() { - const nextPos = node.computedPosition - - if (snapToGrid.value) { - nextPos.x = snapGrid.value[0] * Math.round(nextPos.x / snapGrid.value[0]) - nextPos.y = snapGrid.value[1] * Math.round(nextPos.y / snapGrid.value[1]) - } - - const { computedPosition, position } = calcNextPosition(node, nextPos, emits.error, nodeExtent.value, parentNode.value) + const nextPosition = node.computedPosition + + const { computedPosition, position } = calcNextPosition( + node, + snapToGrid.value ? snapPosition(nextPosition, snapGrid.value) : nextPosition, + emits.error, + nodeExtent.value, + parentNode.value, + ) // only overwrite positions if there are changes when clamping if (node.computedPosition.x !== computedPosition.x || node.computedPosition.y !== computedPosition.y) { @@ -372,7 +374,7 @@ const NodeWrapper = defineComponent({ return emit.doubleClick({ event, node }) } - function onSelectNode(event: MouseEvent) { + function onSelectNode(event: MouseTouchEvent) { if (isSelectable.value && (!selectNodesOnDrag.value || !isDraggable.value || nodeDragThreshold.value > 0)) { handleNodeClick( node, diff --git a/packages/core/src/components/Nodes/OutputNode.ts b/packages/core/src/components/Nodes/OutputNode.ts index 02fb7933b..a0cffdc18 100644 --- a/packages/core/src/components/Nodes/OutputNode.ts +++ b/packages/core/src/components/Nodes/OutputNode.ts @@ -1,5 +1,5 @@ import type { Component, FunctionalComponent } from 'vue' -import { h } from 'vue' +import { Fragment, h } from 'vue' import Handle from '../Handle/Handle.vue' import type { NodeProps } from '../../types' import { Position } from '../../types' @@ -15,7 +15,7 @@ const OutputNode: FunctionalComponent> = function ({ return [ h(Handle as Component, { type: 'target', position: targetPosition, connectable, isValidConnection: isValidTargetPos }), - typeof label !== 'string' && label ? h(label) : h('div', { innerHTML: label }), + typeof label !== 'string' && label ? h(label) : h(Fragment, [label]), ] } diff --git a/packages/core/src/composables/useDrag.ts b/packages/core/src/composables/useDrag.ts index b505d334e..65f9fbfb3 100644 --- a/packages/core/src/composables/useDrag.ts +++ b/packages/core/src/composables/useDrag.ts @@ -3,7 +3,7 @@ import { drag } from 'd3-drag' import { select } from 'd3-selection' import type { MaybeRefOrGetter, Ref } from 'vue' import { ref, toValue, watch } from 'vue' -import type { NodeDragEvent, NodeDragItem, XYPosition } from '../types' +import type { MouseTouchEvent, NodeDragEvent, NodeDragItem, XYPosition } from '../types' import { calcAutoPan, calcNextPosition, @@ -12,6 +12,8 @@ import { getEventPosition, handleNodeClick, hasSelector, + isUseDragEvent, + snapPosition, } from '../utils' import { useGetPointerPosition, useVueFlow } from '.' @@ -21,7 +23,7 @@ interface UseDragParams { onStart: (event: NodeDragEvent) => void onDrag: (event: NodeDragEvent) => void onStop: (event: NodeDragEvent) => void - onClick?: (event: MouseEvent) => void + onClick?: (event: MouseTouchEvent) => void el: Ref disabled?: MaybeRefOrGetter selectable?: MaybeRefOrGetter @@ -87,14 +89,9 @@ export function useDrag(params: UseDragParams) { dragItems = dragItems.map((n) => { const nextPosition = { x: x - n.distance.x, y: y - n.distance.y } - if (snapToGrid.value) { - nextPosition.x = snapGrid.value[0] * Math.round(nextPosition.x / snapGrid.value[0]) - nextPosition.y = snapGrid.value[1] * Math.round(nextPosition.y / snapGrid.value[1]) - } - const { computedPosition } = calcNextPosition( n, - nextPosition, + snapToGrid.value ? snapPosition(nextPosition, snapGrid.value) : nextPosition, emits.error, nodeExtent.value, n.parentNode ? findNode(n.parentNode) : undefined, @@ -171,7 +168,7 @@ export function useDrag(params: UseDragParams) { ) } - const pointerPos = getPointerPosition(event) + const pointerPos = getPointerPosition(event.sourceEvent) lastPos = pointerPos dragItems = getDragItems(nodes.value, nodesDraggable.value, pointerPos, findNode, id) @@ -195,14 +192,14 @@ export function useDrag(params: UseDragParams) { startDrag(event, nodeEl) } - lastPos = getPointerPosition(event) + lastPos = getPointerPosition(event.sourceEvent) containerBounds = vueFlowRef.value?.getBoundingClientRect() || null mousePosition = getEventPosition(event.sourceEvent, containerBounds!) } const eventDrag = (event: UseDragEvent, nodeEl: Element) => { - const pointerPos = getPointerPosition(event) + const pointerPos = getPointerPosition(event.sourceEvent) if (!autoPanStarted && dragStarted && autoPanOnNodeDrag.value) { autoPanStarted = true @@ -229,8 +226,10 @@ export function useDrag(params: UseDragParams) { } const eventEnd = (event: UseDragEvent) => { - if (!dragStarted && !dragging.value && !multiSelectionActive.value) { - const pointerPos = getPointerPosition(event) + if (!isUseDragEvent(event) && !dragStarted && !dragging.value && !multiSelectionActive.value) { + const evt = event as MouseTouchEvent + + const pointerPos = getPointerPosition(evt) const x = pointerPos.xSnapped - (lastPos.x ?? 0) const y = pointerPos.ySnapped - (lastPos.y ?? 0) @@ -238,7 +237,7 @@ export function useDrag(params: UseDragParams) { // dispatch a click event if the node was attempted to be dragged but the threshold was not exceeded if (distance !== 0 && distance <= nodeDragThreshold.value) { - onClick?.(event.sourceEvent) + onClick?.(evt) } return diff --git a/packages/core/src/composables/useGetPointerPosition.ts b/packages/core/src/composables/useGetPointerPosition.ts index 7ff500e09..6715b22c7 100644 --- a/packages/core/src/composables/useGetPointerPosition.ts +++ b/packages/core/src/composables/useGetPointerPosition.ts @@ -1,5 +1,7 @@ -import type { UseDragEvent } from './useDrag' +import { getEventPosition, isUseDragEvent, pointToRendererPoint, snapPosition } from '../utils' +import type { MouseTouchEvent } from '../types' import { useVueFlow } from './useVueFlow' +import type { UseDragEvent } from './useDrag' /** * Composable that returns a function to get the pointer position @@ -10,19 +12,17 @@ export function useGetPointerPosition() { const { viewport, snapGrid, snapToGrid } = useVueFlow() // returns the pointer position projected to the VF coordinate system - return ({ sourceEvent }: UseDragEvent) => { - const x = sourceEvent.touches ? sourceEvent.touches[0].clientX : sourceEvent.clientX - const y = sourceEvent.touches ? sourceEvent.touches[0].clientY : sourceEvent.clientY + return (event: UseDragEvent | MouseTouchEvent) => { + const evt = isUseDragEvent(event) ? event.sourceEvent : event - const pointerPos = { - x: (x - viewport.value.x) / viewport.value.zoom, - y: (y - viewport.value.y) / viewport.value.zoom, - } + const { x, y } = getEventPosition(evt) + const pointerPos = pointToRendererPoint({ x, y }, viewport.value) + const { x: xSnapped, y: ySnapped } = snapToGrid.value ? snapPosition(pointerPos, snapGrid.value) : pointerPos // we need the snapped position in order to be able to skip unnecessary drag events return { - xSnapped: snapToGrid.value ? snapGrid.value[0] * Math.round(pointerPos.x / snapGrid.value[0]) : pointerPos.x, - ySnapped: snapToGrid.value ? snapGrid.value[1] * Math.round(pointerPos.y / snapGrid.value[1]) : pointerPos.y, + xSnapped, + ySnapped, ...pointerPos, } } diff --git a/packages/core/src/composables/useKeyPress.ts b/packages/core/src/composables/useKeyPress.ts index 02cd4e8fe..a38afdf72 100644 --- a/packages/core/src/composables/useKeyPress.ts +++ b/packages/core/src/composables/useKeyPress.ts @@ -3,6 +3,9 @@ import { onMounted, ref, toRef, toValue, watch } from 'vue' import type { KeyFilter, KeyPredicate } from '@vueuse/core' import { onKeyStroke, useEventListener } from '@vueuse/core' +type PressedKeys = Set +type KeyOrCode = 'key' | 'code' + export interface UseKeyPressOptions { actInsideInputWithModifier?: MaybeRefOrGetter target?: MaybeRefOrGetter @@ -25,24 +28,33 @@ function wasModifierPressed(event: KeyboardEvent) { } function isKeyMatch(pressedKey: string, keyToMatch: string, pressedKeys: Set, isKeyUp: boolean) { - const keyCombination = keyToMatch.split('+').map((k) => k.trim().toLowerCase()) + const keyCombination = keyToMatch + .replace('+', '\n') + .replace('\n\n', '\n+') + .split('\n') + .map((k) => k.trim().toLowerCase()) if (keyCombination.length === 1) { return pressedKey.toLowerCase() === keyToMatch.toLowerCase() } - if (isKeyUp) { - pressedKeys.delete(pressedKey.toLowerCase()) - } else { + // we need to remove the key *after* checking for a match otherwise a combination like 'shift+a' would never get unmatched/reset + if (!isKeyUp) { pressedKeys.add(pressedKey.toLowerCase()) } - return keyCombination.every( + const isMatch = keyCombination.every( (key, index) => pressedKeys.has(key) && Array.from(pressedKeys.values())[index] === keyCombination[index], ) + + if (isKeyUp) { + pressedKeys.delete(pressedKey.toLowerCase()) + } + + return isMatch } -function createKeyPredicate(keyFilter: string | string[], pressedKeys: Set): KeyPredicate { +function createKeyPredicate(keyFilter: string | string[], pressedKeys: PressedKeys): KeyPredicate { return (event: KeyboardEvent) => { if (!event.code && !event.key) { return false @@ -60,11 +72,7 @@ function createKeyPredicate(keyFilter: string | string[], pressedKeys: Set