fix: duplicate setState on vue flow mount
# What's changed? * Set props on watcher initialization, so we avoid duplicate setStates (from composable and watchers) * Fix pausable watchers not starting up * Flush watchers on post
This commit is contained in:
@@ -23,64 +23,18 @@ const props = withDefaults(defineProps<FlowProps>(), {
|
|||||||
applyDefault: undefined,
|
applyDefault: undefined,
|
||||||
fitViewOnInit: undefined,
|
fitViewOnInit: undefined,
|
||||||
connectOnClick: undefined,
|
connectOnClick: undefined,
|
||||||
connectionLineStyle: null,
|
connectionLineStyle: undefined,
|
||||||
})
|
})
|
||||||
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:edges', 'update:nodes'])
|
|
||||||
|
|
||||||
const {
|
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:nodes', 'update:edges'])
|
||||||
id,
|
|
||||||
store,
|
const modelProps = useVModels(props, emit)
|
||||||
hooks,
|
|
||||||
applyDefault,
|
const { id, hooks, getNodeTypes, getEdgeTypes, ...rest } = useVueFlow()
|
||||||
setElements,
|
|
||||||
setEdges,
|
useWatch(modelProps, { id, hooks, getNodeTypes, getEdgeTypes, ...rest })
|
||||||
setNodes,
|
|
||||||
getNodeTypes,
|
|
||||||
getEdgeTypes,
|
|
||||||
onNodesChange,
|
|
||||||
onEdgesChange,
|
|
||||||
applyNodeChanges,
|
|
||||||
applyEdgeChanges,
|
|
||||||
nodes: storedNodes,
|
|
||||||
edges: storedEdges,
|
|
||||||
} = useVueFlow(props)
|
|
||||||
useHooks(emit, hooks.value)
|
useHooks(emit, hooks.value)
|
||||||
const { modelValue, nodes, edges } = useVModels(props, emit)
|
|
||||||
onMounted(() => useWatch({ modelValue, nodes, edges }, props, store))
|
|
||||||
|
|
||||||
if (applyDefault.value) {
|
|
||||||
onNodesChange(applyNodeChanges)
|
|
||||||
onEdgesChange(applyEdgeChanges)
|
|
||||||
}
|
|
||||||
if (props.modelValue && !storedNodes.value.length) setElements(props.modelValue)
|
|
||||||
if (props.nodes && !storedNodes.value.length) setNodes(props.nodes)
|
|
||||||
if (props.edges && !storedEdges.value.length) setEdges(props.edges)
|
|
||||||
|
|
||||||
if (modelValue && modelValue.value) {
|
|
||||||
watch(
|
|
||||||
[() => storedEdges.value.length, () => storedNodes.value.length],
|
|
||||||
() => {
|
|
||||||
modelValue.value = [...storedNodes.value, ...storedEdges.value]
|
|
||||||
},
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nodes && nodes.value) {
|
|
||||||
watch(
|
|
||||||
() => storedNodes.value.length,
|
|
||||||
() => (nodes.value = [...storedNodes.value]),
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (edges && edges.value) {
|
|
||||||
watch(
|
|
||||||
() => storedEdges.value.length,
|
|
||||||
() => (edges.value = [...storedEdges.value]),
|
|
||||||
{ immediate: true },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
provide(Slots, useSlots())
|
provide(Slots, useSlots())
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,76 +1,187 @@
|
|||||||
import { Ref } from 'vue'
|
import { Ref, ToRefs } from 'vue'
|
||||||
import { FlowProps, Store, Node, Edge, Elements } from '~/types'
|
import { WatchPausableReturn } from '@vueuse/core'
|
||||||
|
import { FlowProps, UseVueFlow } from '~/types'
|
||||||
|
|
||||||
const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
|
const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
|
||||||
export default (
|
export default (models: ToRefs<FlowProps>, store: UseVueFlow) => {
|
||||||
{
|
|
||||||
modelValue,
|
|
||||||
nodes,
|
|
||||||
edges,
|
|
||||||
}: {
|
|
||||||
modelValue?: Ref<Elements | undefined>
|
|
||||||
nodes?: Ref<Node[] | undefined>
|
|
||||||
edges?: Ref<Edge[] | undefined>
|
|
||||||
},
|
|
||||||
props: FlowProps,
|
|
||||||
store: Store,
|
|
||||||
) => {
|
|
||||||
const scope = getCurrentScope()
|
const scope = getCurrentScope()
|
||||||
|
|
||||||
scope?.run(() => {
|
scope?.run(() => {
|
||||||
if (isDefined(props.modelValue)) {
|
const watchModelValue = () => {
|
||||||
const { pause, resume } = pausableWatch([() => props.modelValue, () => props.modelValue?.length], async ([v]) => {
|
scope.run(() => {
|
||||||
if (v && Array.isArray(v)) {
|
let pauseModel: WatchPausableReturn
|
||||||
pause()
|
let pauseStore: WatchPausableReturn
|
||||||
store.setElements(v)
|
|
||||||
if (modelValue) modelValue.value = [...store.nodes, ...store.edges]
|
|
||||||
await nextTick()
|
|
||||||
resume()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (isDefined(props.nodes)) {
|
|
||||||
const { pause, resume } = pausableWatch([() => props.nodes, () => props.nodes?.length], async ([v]) => {
|
|
||||||
if (v && Array.isArray(v)) {
|
|
||||||
pause()
|
|
||||||
store.setNodes(v)
|
|
||||||
if (nodes) nodes.value = store.nodes
|
|
||||||
await nextTick()
|
|
||||||
resume()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (isDefined(props.edges)) {
|
|
||||||
const { pause, resume } = pausableWatch([() => props.edges, () => props.edges?.length], async ([v]) => {
|
|
||||||
if (v && Array.isArray(v)) {
|
|
||||||
pause()
|
|
||||||
store.setEdges(v)
|
|
||||||
if (edges) edges.value = store.edges
|
|
||||||
await nextTick()
|
|
||||||
resume()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
// eslint-disable-next-line prefer-const
|
||||||
() => props.maxZoom,
|
pauseModel = watchPausable(
|
||||||
(v) => isDef(v) && store.setMaxZoom(v),
|
[models.modelValue, () => models.modelValue?.value?.length],
|
||||||
{ immediate: isDef(props.maxZoom) },
|
([v]) => {
|
||||||
)
|
if (v && Array.isArray(v)) {
|
||||||
watch(
|
if (pauseStore) pauseStore.pause()
|
||||||
() => props.minZoom,
|
if (pauseModel) pauseModel.pause()
|
||||||
(v) => isDef(v) && store.setMinZoom(v),
|
|
||||||
{ immediate: isDef(props.minZoom) },
|
|
||||||
)
|
|
||||||
|
|
||||||
const skip = ['modelValue', 'edges', 'nodes', 'maxZoom', 'minZoom']
|
store.setElements(v)
|
||||||
Object.keys(props).forEach((p) => {
|
|
||||||
if (!skip.includes(p)) {
|
pauseStore = watchPausable(
|
||||||
const prop = props[p as keyof typeof props]
|
[() => store.edges.value.length, () => store.nodes.value.length],
|
||||||
watch(
|
() => {
|
||||||
() => prop,
|
models.modelValue!.value = [...store.nodes.value, ...store.edges.value]
|
||||||
(v) => isDef(v) && ((store as any)[p] = v),
|
},
|
||||||
{ immediate: isDef(prop) },
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (pauseStore) pauseStore.resume()
|
||||||
|
if (pauseModel) pauseModel.resume()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: !!(models.modelValue && models.modelValue.value), flush: 'post' },
|
||||||
)
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const watchNodesValue = () => {
|
||||||
|
scope.run(() => {
|
||||||
|
let pauseModel: WatchPausableReturn
|
||||||
|
let pauseStore: WatchPausableReturn
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
pauseModel = watchPausable(
|
||||||
|
[models.nodes, () => models.nodes?.value?.length],
|
||||||
|
async ([v]) => {
|
||||||
|
if (v && Array.isArray(v)) {
|
||||||
|
if (pauseStore) pauseStore.pause()
|
||||||
|
if (pauseModel) pauseModel.pause()
|
||||||
|
|
||||||
|
store.setNodes(v)
|
||||||
|
|
||||||
|
pauseStore = watchPausable(
|
||||||
|
() => store.nodes.value.length,
|
||||||
|
() => (models.nodes!.value = [...store.nodes.value]),
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (pauseStore) pauseStore.resume()
|
||||||
|
if (pauseModel) pauseModel.resume()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: !!(models.nodes && models.nodes.value), flush: 'post' },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const watchEdgesValue = () => {
|
||||||
|
scope.run(() => {
|
||||||
|
let pauseModel: WatchPausableReturn
|
||||||
|
let pauseStore: WatchPausableReturn
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
pauseModel = watchPausable(
|
||||||
|
[models.edges, () => models.edges?.value?.length],
|
||||||
|
async ([v]) => {
|
||||||
|
if (v && Array.isArray(v)) {
|
||||||
|
if (pauseStore) pauseStore.pause()
|
||||||
|
if (pauseModel) pauseModel.pause()
|
||||||
|
|
||||||
|
store.setEdges(v)
|
||||||
|
|
||||||
|
pauseStore = watchPausable(
|
||||||
|
() => store.edges.value.length,
|
||||||
|
() => (models.edges!.value = [...store.edges.value]),
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
if (pauseStore) pauseStore.resume()
|
||||||
|
if (pauseModel) pauseModel.resume()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: !!(models.edges && models.edges.value), flush: 'post' },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const watchMaxZoom = () => {
|
||||||
|
scope.run(() => {
|
||||||
|
watch(
|
||||||
|
[() => models.maxZoom, models.maxZoom],
|
||||||
|
() => {
|
||||||
|
if (models.maxZoom && isDef(models.maxZoom.value)) {
|
||||||
|
store.setMaxZoom(models.maxZoom.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: isDef(models.maxZoom?.value) },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const watchMinZoom = () => {
|
||||||
|
scope.run(() => {
|
||||||
|
watch(
|
||||||
|
[() => models.minZoom, models.minZoom],
|
||||||
|
() => {
|
||||||
|
if (models.minZoom && isDef(models.minZoom.value)) {
|
||||||
|
store.setMinZoom(models.minZoom.value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: isDef(models.minZoom?.value) },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const watchApplyDefault = () => {
|
||||||
|
scope.run(() => {
|
||||||
|
watch(
|
||||||
|
[() => models.applyDefault, models.applyDefault],
|
||||||
|
() => {
|
||||||
|
if (models.applyDefault && isDef(models.applyDefault.value)) {
|
||||||
|
store.applyDefault.value = models.applyDefault.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: isDef(models.applyDefault?.value) },
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
store.applyDefault,
|
||||||
|
() => {
|
||||||
|
if (store.applyDefault.value) {
|
||||||
|
store.onNodesChange(store.applyNodeChanges)
|
||||||
|
store.onEdgesChange(store.applyEdgeChanges)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watchModelValue()
|
||||||
|
watchNodesValue()
|
||||||
|
watchEdgesValue()
|
||||||
|
watchMaxZoom()
|
||||||
|
watchMinZoom()
|
||||||
|
watchApplyDefault()
|
||||||
|
|
||||||
|
const skip = ['id', 'modelValue', 'edges', 'nodes', 'maxZoom', 'minZoom', 'applyDefault']
|
||||||
|
Object.keys(models).forEach((m) => {
|
||||||
|
if (!skip.includes(m)) {
|
||||||
|
const model = models[m as keyof typeof models]
|
||||||
|
const storedValue = (<any>store)[m] as Ref
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
watch(
|
||||||
|
[() => model, model],
|
||||||
|
() => {
|
||||||
|
if (model && isDef(model.value)) {
|
||||||
|
storedValue.value = model.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: isDef(model?.value) },
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
|
|||||||
const defaultState = (): State => ({
|
const defaultState = (): State => ({
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
|
nodeTypes: {},
|
||||||
|
edgeTypes: {},
|
||||||
|
|
||||||
initialized: false,
|
initialized: false,
|
||||||
instance: null,
|
instance: null,
|
||||||
|
|||||||
Reference in New Issue
Block a user