feat(editor): Improve UX of embedded NDV experiment (no-changelog) (#17515)

This commit is contained in:
Suguru Inoue
2025-07-23 14:04:16 +02:00
committed by GitHub
parent 4b2be26379
commit 7330e2d0af
16 changed files with 223 additions and 89 deletions

View File

@@ -1569,6 +1569,8 @@
"nodeView.setupTemplate": "Set up template",
"nodeView.expandAllNodes": "Expand all nodes",
"nodeView.collapseAllNodes": "Collapse all nodes",
"nodeView.enterZoomMode": "Enter zoom mode",
"nodeView.leaveZoomMode": "Leave zoom mode",
"nodeViewV2.showError.editingNotAllowed": "Editing is not allowed",
"nodeViewV2.showError.failedToCreateNode": "Failed to create node",
"contextMenu.node": "node | nodes",

View File

@@ -50,7 +50,7 @@
"@typescript/vfs": "^1.6.0",
"@vue-flow/background": "^1.3.2",
"@vue-flow/controls": "^1.1.2",
"@vue-flow/core": "^1.42.1",
"@vue-flow/core": "^1.45.0",
"@vue-flow/minimap": "^1.5.2",
"@vue-flow/node-resizer": "^1.4.0",
"@vueuse/components": "^10.11.0",

View File

@@ -92,7 +92,10 @@ export function createCanvasGraphNode({
isParent: false,
selected: false,
resizing: false,
handleBounds: {},
handleBounds: {
source: null,
target: null,
},
events: {},
data: createCanvasNodeData({ id, type, ...data }),
...rest,

View File

@@ -70,7 +70,6 @@ const props = withDefaults(
inputSize: number;
activeNode?: INodeUi;
isEmbeddedInCanvas?: boolean;
noWheel?: boolean;
subTitle?: string;
}>(),
{
@@ -81,7 +80,6 @@ const props = withDefaults(
blockUI: false,
activeNode: undefined,
isEmbeddedInCanvas: false,
noWheel: false,
subTitle: undefined,
},
);
@@ -98,6 +96,7 @@ const emit = defineEmits<{
];
activate: [];
execute: [];
captureWheelBody: [WheelEvent];
}>();
const slots = defineSlots<{ actions?: {} }>();
@@ -814,12 +813,6 @@ function displayCredentials(credentialTypeDescription: INodeCredentialDescriptio
nodeHelpers.displayParameter(node.value.parameters, credentialTypeDescription, '', node.value)
);
}
function handleWheelEvent(event: WheelEvent) {
if (event.ctrlKey) {
event.preventDefault();
}
}
</script>
<template>
@@ -941,11 +934,10 @@ function handleWheelEvent(event: WheelEvent) {
:class="[
'node-parameters-wrapper',
shouldShowStaticScrollbar ? 'with-static-scrollbar' : '',
noWheel && shouldShowStaticScrollbar ? 'nowheel' : '',
{ 'ndv-v2': isNDVV2 },
]"
data-test-id="node-parameters"
@wheel="noWheel ? handleWheelEvent : undefined"
@wheel.capture="emit('captureWheelBody', $event)"
>
<N8nNotice
v-if="hasForeignCredential && !isHomeProjectTeam"
@@ -1148,7 +1140,7 @@ function handleWheelEvent(event: WheelEvent) {
}
&.embedded .node-parameters-wrapper.with-static-scrollbar {
padding: 0 var(--spacing-2xs) var(--spacing-xs) var(--spacing-xs);
padding: 0 var(--spacing-4xs) var(--spacing-xs) var(--spacing-xs);
@supports not (selector(::-webkit-scrollbar)) {
scrollbar-width: thin;

View File

@@ -160,6 +160,7 @@ const {
nodesSelectionActive,
userSelectionRect,
setViewport,
setCenter,
onEdgeMouseLeave,
onEdgeMouseEnter,
onEdgeMouseMove,
@@ -285,6 +286,18 @@ function selectUpstreamNodes(id: string) {
onSelectNodes({ ids: [...upstreamNodes.map((node) => node.id), id] });
}
function onToggleZoomMode() {
experimentalNdvStore.toggleZoomMode({
canvasViewport: viewport.value,
canvasDimensions: dimensions.value,
selectedNodes: selectedNodes.value,
setViewport,
fitView,
zoomTo,
setCenter,
});
}
const keyMap = computed(() => {
const readOnlyKeymap: KeyMap = {
ctrl_shift_o: emitWithLastSelectedNode((id) => emit('open:sub-workflow', id)),
@@ -308,6 +321,7 @@ const keyMap = computed(() => {
l: () => emit('update:logs-open'),
i: () => emit('update:logs:input-open'),
o: () => emit('update:logs:output-open'),
z: onToggleZoomMode,
};
if (props.readOnly) return readOnlyKeymap;
@@ -431,7 +445,7 @@ function onSelectNodes({ ids, panIntoView }: CanvasEventBusEvents['nodes:select'
const newViewport = updateViewportToContainNodes(viewport.value, dimensions.value, nodes, 100);
void setViewport(newViewport, { duration: 200 });
void setViewport(newViewport, { duration: 200, interpolate: 'linear' });
}
}
@@ -455,6 +469,18 @@ function onUpdateNodeOutputs(id: string) {
emit('update:node:outputs', id);
}
function onFocusNode(id: string) {
const node = vueFlow.nodeLookup.value.get(id);
if (node) {
experimentalNdvStore.focusNode(node, {
canvasViewport: viewport.value,
canvasDimensions: dimensions.value,
setCenter,
});
}
}
/**
* Connections / Edges
*/
@@ -896,7 +922,6 @@ provide(CanvasKey, {
:event-bus="eventBus"
:hovered="nodesHoveredById[nodeProps.id]"
:nearby-hovered="nodeProps.id === hoveredTriggerNode.id.value"
:is-experimental-ndv-active="isExperimentalNdvActive"
@delete="onDeleteNode"
@run="onRunNode"
@select="onSelectNode"
@@ -909,6 +934,7 @@ provide(CanvasKey, {
@update:outputs="onUpdateNodeOutputs"
@move="onUpdateNodePosition"
@add="onClickNodeAdd"
@focus="onFocusNode"
>
<template v-if="$slots.nodeToolbar" #toolbar="toolbarProps">
<slot name="nodeToolbar" v-bind="toolbarProps" />
@@ -972,6 +998,7 @@ provide(CanvasKey, {
@zoom-out="onZoomOut"
@reset-zoom="onResetZoom"
@tidy-up="onTidyUp({ source: 'canvas-button' })"
@toggle-zoom-mode="onToggleZoomMode"
/>
<Suspense>
@@ -1004,7 +1031,7 @@ provide(CanvasKey, {
}
&.isExperimentalNdvActive {
--canvas-zoom-compensation-factor: 0.67;
--canvas-zoom-compensation-factor: 0.5;
}
}
</style>

View File

@@ -5,12 +5,12 @@ import { useI18n } from '@n8n/i18n';
import { Controls } from '@vue-flow/controls';
import { computed } from 'vue';
import { useExperimentalNdvStore } from '../../experimental/experimentalNdv.store';
import { N8nIconButton } from '@n8n/design-system';
const props = withDefaults(
defineProps<{
zoom?: number;
readOnly?: boolean;
isExperimentalNdvActive: boolean;
}>(),
{
zoom: 1,
@@ -24,13 +24,18 @@ const emit = defineEmits<{
'zoom-out': [];
'zoom-to-fit': [];
'tidy-up': [];
'toggle-zoom-mode': [];
}>();
const i18n = useI18n();
const experimentalNdvStore = useExperimentalNdvStore();
const isResetZoomVisible = computed(() => props.zoom !== 1);
const isExperimentalNdvActive = computed(() => experimentalNdvStore.isActive(props.zoom));
const isToggleZoomVisible = computed(() => experimentalNdvStore.isEnabled);
const isResetZoomVisible = computed(() => !isToggleZoomVisible.value && props.zoom !== 1);
function onResetZoom() {
emit('reset-zoom');
@@ -84,6 +89,22 @@ function onTidyUp() {
@click="onZoomOut"
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
v-if="isToggleZoomVisible"
:label="
i18n.baseText(isExperimentalNdvActive ? 'nodeView.leaveZoomMode' : 'nodeView.enterZoomMode')
"
:shortcut="{ keys: ['Z'] }"
>
<N8nIconButton
square
type="tertiary"
size="large"
:class="$style.iconButton"
:icon="isExperimentalNdvActive ? 'undo-2' : 'crosshair'"
@click="emit('toggle-zoom-mode')"
/>
</KeyboardShortcutTooltip>
<KeyboardShortcutTooltip
v-if="isResetZoomVisible"
:label="i18n.baseText('nodeView.resetZoom')"

View File

@@ -18,6 +18,7 @@ exports[`CanvasControlButtons > should render correctly 1`] = `
<!--teleport start-->
<!--teleport end-->
<!--v-if-->
<!--v-if-->
<n8n-button-stub block="false" element="button" label="" square="true" active="false" disabled="false" loading="false" outline="false" size="large" text="false" type="tertiary" data-test-id="tidy-up-button" class="iconButton el-tooltip__trigger el-tooltip__trigger"></n8n-button-stub>
<!--teleport start-->
<!--teleport end-->

View File

@@ -64,6 +64,7 @@ const emit = defineEmits<{
'update:inputs': [id: string];
'update:outputs': [id: string];
move: [id: string, position: XYPosition];
focus: [id: string];
}>();
const style = useCssModule();
@@ -270,6 +271,10 @@ function onMove(position: XYPosition) {
emit('move', props.id, position);
}
function onFocus(id: string) {
emit('focus', id);
}
function onUpdateClass({ className, add = true }: CanvasNodeEventBusEvents['update:node:class']) {
nodeClasses.value = add
? [...new Set([...nodeClasses.value, className])]
@@ -393,6 +398,7 @@ onBeforeUnmount(() => {
@run="onRun"
@update="onUpdate"
@open:contextmenu="onOpenContextMenuFromToolbar"
@focus="onFocus"
/>
<CanvasNodeRenderer

View File

@@ -14,6 +14,7 @@ const emit = defineEmits<{
run: [];
update: [parameters: Record<string, unknown>];
'open:contextmenu': [event: MouseEvent];
focus: [id: string];
}>();
const props = defineProps<{
@@ -105,7 +106,7 @@ function onMouseLeave() {
function onFocusNode() {
if (node.value) {
experimentalNdvStore.focusNode(node.value.id);
emit('focus', node.value.id);
}
}
</script>
@@ -189,6 +190,7 @@ function onFocusNode() {
&.isExperimentalNdvActive {
justify-content: center;
padding-bottom: var(--spacing-3xs);
}
}

View File

@@ -6,9 +6,8 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { createEventBus } from '@n8n/utils/event-bus';
import { computed } from 'vue';
const { nodeId, noWheel, isReadOnly, subTitle } = defineProps<{
const { nodeId, isReadOnly, subTitle } = defineProps<{
nodeId: string;
noWheel?: boolean;
isReadOnly?: boolean;
subTitle?: string;
}>();
@@ -26,6 +25,29 @@ function handleValueChanged(parameterData: IUpdateInformation) {
void renameNode(parameterData.oldValue as string, parameterData.value as string);
}
}
function handleCaptureWheelEvent(event: WheelEvent) {
if (event.ctrlKey) {
// If the event is pinch, let it propagate and zoom canvas
return;
}
if (
event.currentTarget instanceof HTMLElement &&
event.currentTarget.scrollHeight <= event.currentTarget.offsetHeight
) {
// If the settings pane doesn't have to scroll, let it propagate and move the canvas
return;
}
// If the event has larger horizontal element, let it propagate and move the canvas
if (Math.abs(event.deltaX) >= Math.abs(event.deltaY)) {
return;
}
// Otherwise, let it scroll the settings pane
event.stopImmediatePropagation();
}
</script>
<template>
@@ -40,9 +62,9 @@ function handleValueChanged(parameterData: IUpdateInformation) {
:executable="false"
:input-size="0"
is-embedded-in-canvas
:no-wheel="noWheel"
:sub-title="subTitle"
@value-changed="handleValueChanged"
@capture-wheel-body="handleCaptureWheelEvent"
>
<template #actions>
<slot name="actions" />

View File

@@ -75,6 +75,9 @@ onBeforeUnmount(() => {
:width="360"
:offset="8"
:append-to="vf.viewportRef?.value"
:popper-options="{
modifiers: [{ name: 'flip', enabled: false }],
}"
>
<template #reference>
<slot />

View File

@@ -58,8 +58,8 @@ const isVisible = computed(() =>
{
x: -vf.viewport.value.x / vf.viewport.value.zoom,
y: -vf.viewport.value.y / vf.viewport.value.zoom,
width: vf.viewportRef.value?.offsetWidth ?? 0,
height: vf.viewportRef.value?.offsetHeight ?? 0,
width: vf.dimensions.value.width,
height: vf.dimensions.value.height,
},
),
);
@@ -148,6 +148,8 @@ watchOnce(isVisible, (visible) => {
:style="{
'--zoom': `${1 / experimentalNdvStore.maxCanvasZoom}`,
'--node-width-scaler': isConfigurable ? 1 : 1.5,
'--max-height-on-focus': `${(vf.dimensions.value.height * 0.8) / experimentalNdvStore.maxCanvasZoom}px`,
pointerEvents: isMoving ? 'none' : 'auto', // Don't interrupt canvas panning
}"
>
<template v-if="!node || !isOnceVisible" />
@@ -162,9 +164,6 @@ watchOnce(isVisible, (visible) => {
tabindex="-1"
:node-id="nodeId"
:class="$style.settingsView"
:no-wheel="
!isMoving /* to not interrupt panning while allowing scroll of the settings pane, allow wheel event while panning */
"
:is-read-only="isReadOnly"
:sub-title="subTitle"
>
@@ -197,23 +196,31 @@ watchOnce(isVisible, (visible) => {
</template>
<style lang="scss" module>
:root .component {
position: relative;
.component {
align-items: flex-start;
justify-content: stretch;
border-width: 1px !important;
border-radius: var(--border-radius-base) !important;
width: calc(var(--canvas-node--width) * var(--node-width-scaler));
width: calc(var(--canvas-node--width) * var(--node-width-scaler)) !important;
overflow: hidden;
--canvas-node--border-color: var(--color-text-lighter);
--expanded-max-height: min(
calc(var(--canvas-node--height) * 2),
var(--max-height-on-focus),
300px
);
&.expanded {
user-select: text;
cursor: auto;
height: auto;
max-height: min(calc(var(--canvas-node--height) * 2), 300px);
max-height: var(--expanded-max-height);
min-height: var(--spacing-3xl);
:global(.selected) & {
max-height: var(--max-height-on-focus);
}
}
&.collapsed {
overflow: hidden;
@@ -229,14 +236,18 @@ watchOnce(isVisible, (visible) => {
}
}
:root .collapsedContent,
:root .settingsView {
.collapsedContent,
.settingsView {
z-index: 1000;
width: 100%;
height: auto;
max-height: calc(min(calc(var(--canvas-node--height) * 2), 300px) - var(--border-width-base) * 2);
max-height: calc(var(--expanded-max-height) - var(--border-width-base) * 2);
min-height: var(--spacing-2xl); // should be multiple of GRID_SIZE
:global(.selected) & {
max-height: calc(var(--max-height-on-focus) - var(--border-width-base) * 2);
}
}
.collapsedContent {

View File

@@ -1,9 +1,17 @@
import { computed, shallowRef } from 'vue';
import { computed, ref, shallowRef } from 'vue';
import { defineStore } from 'pinia';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useVueFlow } from '@vue-flow/core';
import { calculateNodeSize } from '@/utils/nodeViewUtils';
import {
type Dimensions,
type FitView,
type GraphNode,
type SetCenter,
type SetViewport,
type ViewportTransform,
type ZoomTo,
} from '@vue-flow/core';
import { CanvasNodeRenderType, type CanvasNodeData } from '@/types';
export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
const workflowStore = useWorkflowsStore();
@@ -17,6 +25,7 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
isEnabled.value ? settingsStore.experimental__minZoomNodeSettingsInCanvas : 4,
);
const previousViewport = ref<ViewportTransform>();
const collapsedNodes = shallowRef<Partial<Record<string, boolean>>>({});
function setNodeExpanded(nodeId: string, isExpanded?: boolean) {
@@ -41,51 +50,89 @@ export const useExperimentalNdvStore = defineStore('experimentalNdv', () => {
}
function isActive(canvasZoom: number) {
return isEnabled.value && canvasZoom === maxCanvasZoom.value;
return isEnabled.value && Math.abs(canvasZoom - maxCanvasZoom.value) < 0.000001;
}
function focusNode(nodeId: string) {
const nodeToFocus = workflowStore.getNodeById(nodeId);
interface FocusNodeOptions {
collapseOthers?: boolean;
canvasViewport: ViewportTransform;
canvasDimensions: Dimensions;
setCenter: SetCenter;
}
if (!nodeToFocus) {
function focusNode(
node: GraphNode<CanvasNodeData>,
{ collapseOthers = true, canvasDimensions, canvasViewport, setCenter }: FocusNodeOptions,
) {
collapsedNodes.value = collapseOthers
? workflowStore.allNodes.reduce<Partial<Record<string, boolean>>>((acc, n) => {
acc[n.id] = n.id !== node.id;
return acc;
}, {})
: { ...collapsedNodes.value, [node.id]: false };
const topMargin = 80; // pixels
const nodeWidth = node.dimensions.width * (isActive(canvasViewport.zoom) ? 1 : 1.5);
// Move the node to top center of the canvas
void setCenter(
node.position.x + nodeWidth / 2,
node.position.y + (canvasDimensions.height * (1 / 2) - topMargin) / maxCanvasZoom.value,
{
duration: 200,
zoom: maxCanvasZoom.value,
interpolate: 'linear',
},
);
}
interface ToggleZoomModeOptions {
canvasViewport: ViewportTransform;
canvasDimensions: Dimensions;
selectedNodes: Array<GraphNode<CanvasNodeData>>;
setViewport: SetViewport;
fitView: FitView;
zoomTo: ZoomTo;
setCenter: SetCenter;
}
function toggleZoomMode(options: ToggleZoomModeOptions) {
if (isActive(options.canvasViewport.zoom)) {
if (previousViewport.value === undefined) {
void options.fitView({ duration: 200, interpolate: 'linear' });
return;
}
void options.setViewport(previousViewport.value, { duration: 200, interpolate: 'linear' });
return;
}
// Call useVueFlow() here because having it in setup fn scope seem to cause initialization problem
const vueFlow = useVueFlow(workflowStore.workflow.id);
previousViewport.value = options.canvasViewport;
collapsedNodes.value = workflowStore.allNodes.reduce<Partial<Record<string, boolean>>>(
(acc, node) => {
acc[node.id] = node.id !== nodeId;
return acc;
},
{},
);
const toFocus = options.selectedNodes
.filter((node) => node.data.render.type === CanvasNodeRenderType.Default)
.toSorted((a, b) =>
a.position.y === b.position.y ? a.position.x - b.position.x : a.position.y - b.position.y,
)[0];
const workflow = workflowStore.getCurrentWorkflow();
const nodeSize = calculateNodeSize(
workflow.getChildNodes(nodeToFocus.name, 'ALL_NON_MAIN').length > 0,
workflow.getParentNodes(nodeToFocus.name, 'ALL_NON_MAIN').length > 0,
workflow.getParentNodes(nodeToFocus.name, 'main').length,
workflow.getChildNodes(nodeToFocus.name, 'main').length,
workflow.getParentNodes(nodeToFocus.name, 'ALL_NON_MAIN').length,
);
if (toFocus) {
focusNode(toFocus, { ...options, collapseOthers: false });
return;
}
void vueFlow.setCenter(
nodeToFocus.position[0] + (nodeSize.width * 1.5) / 2,
nodeToFocus.position[1] + 80,
{ duration: 200, zoom: maxCanvasZoom.value },
);
void options.zoomTo(maxCanvasZoom.value, { duration: 200, interpolate: 'linear' });
}
return {
isEnabled,
maxCanvasZoom,
previousZoom: computed(() => previousViewport.value),
collapsedNodes: computed(() => collapsedNodes.value),
isActive,
setNodeExpanded,
expandAllNodes,
collapseAllNodes,
toggleZoomMode,
focusNode,
};
});

View File

@@ -15,7 +15,6 @@ import { useUIStore } from '@/stores/ui.store';
import { shallowRef, watch } from 'vue';
import { computed, type ComputedRef } from 'vue';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
export function useLogsSelection(
execution: ComputedRef<IExecutionResponse | undefined>,
@@ -34,19 +33,13 @@ export function useLogsSelection(
const uiStore = useUIStore();
const canvasStore = useCanvasStore();
const workflowsStore = useWorkflowsStore();
const experimentalNdvStore = useExperimentalNdvStore();
function syncSelectionToCanvasIfEnabled(value: LogEntry) {
if (!logsStore.isLogSelectionSyncedWithCanvas) {
return;
}
if (experimentalNdvStore.isEnabled) {
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: false });
experimentalNdvStore.focusNode(value.node.id);
} else {
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: true });
}
canvasEventBus.emit('nodes:select', { ids: [value.node.id], panIntoView: true });
}
function select(value: LogEntry | undefined) {

View File

@@ -544,7 +544,10 @@ describe('calculateNodeSize', () => {
function createTestGraphNode(data: Partial<GraphNode> = {}): GraphNode {
return {
computedPosition: { z: 0, ...(data.position ?? { x: 0, y: 0 }) },
handleBounds: {},
handleBounds: {
source: null,
target: null,
},
dimensions: { width: 0, height: 0 },
isParent: true,
selected: false,

35
pnpm-lock.yaml generated
View File

@@ -2360,19 +2360,19 @@ importers:
version: 1.6.0(typescript@5.8.3)
'@vue-flow/background':
specifier: ^1.3.2
version: 1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
version: 1.3.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
'@vue-flow/controls':
specifier: ^1.1.2
version: 1.1.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
version: 1.1.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
'@vue-flow/core':
specifier: ^1.42.1
version: 1.42.1(vue@3.5.13(typescript@5.8.3))
specifier: ^1.45.0
version: 1.45.0(vue@3.5.13(typescript@5.8.3))
'@vue-flow/minimap':
specifier: ^1.5.2
version: 1.5.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
version: 1.5.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
'@vue-flow/node-resizer':
specifier: ^1.4.0
version: 1.4.0(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
version: 1.4.0(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))
'@vueuse/components':
specifier: ^10.11.0
version: 10.11.0(vue@3.5.13(typescript@5.8.3))
@@ -7706,8 +7706,8 @@ packages:
'@vue-flow/core': ^1.23.0
vue: ^3.3.0
'@vue-flow/core@1.42.1':
resolution: {integrity: sha512-QzzTxMAXfOeETKc+N3XMp5XpiPxKBHK5kq98avgTsE6MXyeU2E8EkANwwgSB/hvJ/k36RjU0Y7BOwCHiqiI1tw==}
'@vue-flow/core@1.45.0':
resolution: {integrity: sha512-+Qd4fTnCfrhfYQzlHyf5Jt7rNE4PlDnEJEJZH9v6hDZoTOeOy1RhS85cSxKYxdsJ31Ttj2v3yabhoVfBf+bmJA==}
peerDependencies:
vue: ^3.3.0
@@ -22328,36 +22328,37 @@ snapshots:
path-browserify: 1.0.1
vscode-uri: 3.0.8
'@vue-flow/background@1.3.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
'@vue-flow/background@1.3.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
vue: 3.5.13(typescript@5.8.3)
'@vue-flow/controls@1.1.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
'@vue-flow/controls@1.1.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
vue: 3.5.13(typescript@5.8.3)
'@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3))':
'@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@vueuse/core': 10.11.0(vue@3.5.13(typescript@5.8.3))
d3-drag: 3.0.0
d3-interpolate: 3.0.1
d3-selection: 3.0.0
d3-zoom: 3.0.0
vue: 3.5.13(typescript@5.8.3)
transitivePeerDependencies:
- '@vue/composition-api'
'@vue-flow/minimap@1.5.2(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
'@vue-flow/minimap@1.5.2(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
d3-selection: 3.0.0
d3-zoom: 3.0.0
vue: 3.5.13(typescript@5.8.3)
'@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.42.1(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
'@vue-flow/node-resizer@1.4.0(@vue-flow/core@1.45.0(vue@3.5.13(typescript@5.8.3)))(vue@3.5.13(typescript@5.8.3))':
dependencies:
'@vue-flow/core': 1.42.1(vue@3.5.13(typescript@5.8.3))
'@vue-flow/core': 1.45.0(vue@3.5.13(typescript@5.8.3))
d3-drag: 3.0.0
d3-selection: 3.0.0
vue: 3.5.13(typescript@5.8.3)