docs: add multiple transition animations to teleport example

# What's changed?

* add fade and shrink animations to teleportable nodes
* demonstrate using `updateNodeDimensions` to properly align edges after a resizing transition
This commit is contained in:
bcakmakoglu
2022-06-15 22:38:57 +02:00
parent 2de121509e
commit 597f799fa4
7 changed files with 167 additions and 56 deletions
+2 -1
View File
@@ -28,7 +28,8 @@ body,
.vue-flow__minimap { .vue-flow__minimap {
transform: scale(75%); transform: scale(75%);
transform-origin: bottom right; transform-origin: bottom right;
}` }
\n`
const store = new ReplStore({ const store = new ReplStore({
showOutput: true, showOutput: true,
+2 -1
View File
@@ -14,7 +14,7 @@ import { HiddenApp } from './hidden'
import { InteractionApp, InteractionCSS, InteractionControls } from './interaction' import { InteractionApp, InteractionCSS, InteractionControls } from './interaction'
import { MultiApp, MultiCSS, MultiFlow } from './multi' import { MultiApp, MultiCSS, MultiFlow } from './multi'
import { HorizontalApp, HorizontalElements } from './horizontal' import { HorizontalApp, HorizontalElements } from './horizontal'
import { TeleportApp, TeleportCSS, TeleportSidebar, TeleportableNode } from './teleport' import { TeleportApp, TeleportCSS, TeleportSidebar, TeleportableNode, TeleportableUseTransition } from './teleport'
export const exampleImports = { export const exampleImports = {
basic: { basic: {
@@ -94,6 +94,7 @@ export const exampleImports = {
'App.vue': TeleportApp, 'App.vue': TeleportApp,
'Sidebar.vue': TeleportSidebar, 'Sidebar.vue': TeleportSidebar,
'TeleportableNode.vue': TeleportableNode, 'TeleportableNode.vue': TeleportableNode,
'useTransition.js': TeleportableUseTransition,
'style.css': TeleportCSS, 'style.css': TeleportCSS,
}, },
} }
+2 -2
View File
@@ -1,5 +1,5 @@
<script setup> <script setup>
import { VueFlow, useVueFlow } from '@braks/vue-flow' import { VueFlow } from '@braks/vue-flow'
import { ref } from 'vue' import { ref } from 'vue'
import Sidebar from './Sidebar.vue' import Sidebar from './Sidebar.vue'
import TeleportableNode from './TeleportableNode.vue' import TeleportableNode from './TeleportableNode.vue'
@@ -38,7 +38,7 @@ const elements = ref([
<div class="teleportflow"> <div class="teleportflow">
<VueFlow v-model="elements" :fit-view-on-init="true"> <VueFlow v-model="elements" :fit-view-on-init="true">
<template #node-teleportable="props"> <template #node-teleportable="props">
<TeleportableNode :id="props.id" /> <TeleportableNode v-bind="props" />
</template> </template>
</VueFlow> </VueFlow>
<Sidebar /> <Sidebar />
@@ -1,72 +1,40 @@
<script setup> <script setup>
import { Handle, Position, getConnectedEdges, useVueFlow } from '@braks/vue-flow' import { Handle, Position } from '@braks/vue-flow'
import { ref } from 'vue' import { watch } from 'vue'
import { useTransition } from './useTransition.js'
const props = defineProps({ const props = defineProps({
id: { id: {
type: String, type: String,
required: true, required: true,
}, },
nodeElement: {
type: Object,
required: false,
},
}) })
const { edges, getNode } = useVueFlow() const { animation, transition, teleport, nodeElement, onClick } = useTransition(props.id, props.nodeElement)
const teleport = ref(null) watch(props, () => {
const transition = ref(false) if (props.nodeElement) nodeElement.value = props.nodeElement
})
/** const changeAnimation = () => {
* specify a selector to teleport to animation.value = animation.value === 'fade' ? 'shrink' : 'fade'
*
* teleported elements still behave like they're at their position before,
* i.e. if they emit events, they will still emit them up their regular tree
*/
const onClick = (destination) => {
const node = getNode.value(props.id)
transition.value = true
// save current teleport destination to data of node
node.data.destination = destination
// hide connected edges when teleporting
const connectedEdges = getConnectedEdges([node], edges.value)
// if destination is not null, hide edges immediately
// check if nodes connected to edge are teleported and hide edge if one of them is
if (destination) {
connectedEdges.forEach(
(edge) => (edge.hidden = !!getNode.value(edge.source).data.destination || !!getNode.value(edge.target).data.destination),
)
}
setTimeout(() => {
// teleport to destination or disable teleport
teleport.value = destination
setTimeout(() => {
transition.value = false
// if destination is null, defer hiding edges until node is teleported back
if (!destination) {
connectedEdges.forEach(
(edge) =>
(edge.hidden = !!getNode.value(edge.source).data.destination || !!getNode.value(edge.target).data.destination),
)
}
}, 500)
}, 500)
} }
</script> </script>
<template> <template>
<teleport :disabled="!teleport" :to="teleport"> <teleport :disabled="!teleport" :to="teleport">
<transition name="fade"> <transition :name="animation">
<div v-if="!transition" class="teleportable"> <div v-if="!transition" class="teleportable">
<Handle type="target" :position="Position.Top" /> <Handle type="target" :position="Position.Top" />
[Node {{ id }}] [Node {{ id }}]
<div class="buttons"> <div class="buttons">
<div v-if="teleport !== '#port'" class="button" @click.prevent="onClick('#port')">Teleport To Sidebar</div> <div v-if="teleport !== '#port'" class="button" @click.prevent="onClick('#port')">Teleport To Sidebar</div>
<div v-if="teleport !== null" class="button" @click.prevent="onClick(null)">Teleport To Main Graph</div> <div v-if="teleport !== null" class="button" @click.prevent="onClick(null)">Teleport To Main Graph</div>
<div class="button" @click.prevent="changeAnimation">Animation: {{ animation }}</div>
</div> </div>
<Handle type="source" :position="Position.Bottom" /> <Handle type="source" :position="Position.Bottom" />
</div> </div>
@@ -1,4 +1,5 @@
export { default as TeleportApp } from './App.vue?raw' export { default as TeleportApp } from './App.vue?raw'
export { default as TeleportSidebar } from './Sidebar.vue?raw' export { default as TeleportSidebar } from './Sidebar.vue?raw'
export { default as TeleportableNode } from './TeleportableNode.vue?raw' export { default as TeleportableNode } from './TeleportableNode.vue?raw'
export { default as TeleportableUseTransition } from './useTransition.js?raw'
export { default as TeleportCSS } from './style.css' export { default as TeleportCSS } from './style.css'
+36 -5
View File
@@ -5,10 +5,13 @@
} }
.teleportflow aside { .teleportflow aside {
display: flex;
flex-direction: column;
gap: 5px;
color: white; color: white;
font-weight: 700; font-weight: 700;
border-right: 1px solid #eee; border-right: 1px solid #eee;
padding: 15px 10px; padding: 10px 10px;
font-size: 12px; font-size: 12px;
background: rgba(16, 185, 129, 0.75); background: rgba(16, 185, 129, 0.75);
-webkit-box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3); -webkit-box-shadow: 0px 5px 10px 0px rgba(0, 0, 0, 0.3);
@@ -59,16 +62,17 @@
color: black color: black
} }
.teleportable .buttons { .buttons {
display: flex; display: flex;
flex-direction: row; flex-direction: column;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin-top: 5px; margin-top: 5px;
gap: 5px; gap: 5px;
} }
.teleportable .buttons .button { .button {
background-color: whitesmoke;
cursor: pointer; cursor: pointer;
padding: 5px 10px; padding: 5px 10px;
border: 1px solid black; border: 1px solid black;
@@ -78,7 +82,7 @@
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
} }
.teleportable .buttons .button:hover { .button:hover {
background: black; background: black;
color: white; color: white;
} }
@@ -92,3 +96,30 @@
.fade-leave-to { .fade-leave-to {
opacity: 0; opacity: 0;
} }
.shrink-leave-active {
animation: shrink 0.5s;
}
.shrink-enter-active {
animation: grow 0.5s;
}
@keyframes grow {
from {
transform: scale(0.1);
}
to {
transform: scale(1);
}
}
@keyframes shrink {
from {
transform: scale(1);
}
to {
transform: scale(0.1);
}
}
@@ -0,0 +1,109 @@
import { getConnectedEdges, useVueFlow } from '@braks/vue-flow'
import { nextTick, ref } from 'vue'
/**
* Utility composable for specifying animations
*
* Animations that resize a node need to call the `updateNodeDimensions` function from store to update node handle positions
* Otherwise edges do not connect properly
*/
export const useTransition = (id) => {
const nodeElement = ref()
const animation = ref('fade')
const transition = ref(false)
const teleport = ref(null)
const { updateNodeDimensions, getNode, edges } = useVueFlow()
/**
* specify a selector to teleport to
*
* teleported elements still behave like they're at their position before,
* i.e. if they emit events, they will still emit them up their regular tree
*/
const fade = (destination, onFinish) => {
setTimeout(() => {
// teleport to destination or disable teleport
teleport.value = destination
setTimeout(() => {
transition.value = false
// if destination is null, defer hiding edges until node is teleported back
if (!destination) {
onFinish()
}
}, 500)
}, 500)
}
const shrink = (destination, onFinish) => {
setTimeout(() => {
// teleport to destination or disable teleport
teleport.value = destination
setTimeout(() => {
transition.value = false
setTimeout(() => {
// if destination is null, defer hiding edges until node is teleported back
if (!destination) {
updateNodeDimensions([{ id, nodeElement: nodeElement.value, forceUpdate: true }])
nextTick(() => {
onFinish()
})
}
}, 500)
}, 500)
}, 500)
}
/**
* specify a selector to teleport to
*
* teleported elements still behave like they're at their position before,
* i.e. if they emit events, they will still emit them up their regular tree
*/
const onClick = (destination) => {
const node = getNode.value(id)
transition.value = true
// save current teleport destination to data of node
node.data.destination = destination
// hide connected edges when teleporting
const connectedEdges = getConnectedEdges([node], edges.value)
// if destination is not null, hide edges immediately
// check if nodes connected to edge are teleported and hide edge if one of them is
if (destination) {
connectedEdges.forEach(
(edge) => (edge.hidden = !!getNode.value(edge.source).data.destination || !!getNode.value(edge.target).data.destination),
)
}
const onFinish = () => {
connectedEdges.forEach(
(edge) => (edge.hidden = !!getNode.value(edge.source).data.destination || !!getNode.value(edge.target).data.destination),
)
}
switch (animation.value) {
case 'fade':
fade(destination, onFinish)
break
case 'shrink':
shrink(destination, onFinish)
}
}
return {
animation,
transition,
teleport,
nodeElement,
onClick,
}
}