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,
|
||||
fitViewOnInit: undefined,
|
||||
connectOnClick: undefined,
|
||||
connectionLineStyle: null,
|
||||
connectionLineStyle: undefined,
|
||||
})
|
||||
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:edges', 'update:nodes'])
|
||||
|
||||
const {
|
||||
id,
|
||||
store,
|
||||
hooks,
|
||||
applyDefault,
|
||||
setElements,
|
||||
setEdges,
|
||||
setNodes,
|
||||
getNodeTypes,
|
||||
getEdgeTypes,
|
||||
onNodesChange,
|
||||
onEdgesChange,
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
nodes: storedNodes,
|
||||
edges: storedEdges,
|
||||
} = useVueFlow(props)
|
||||
const emit = defineEmits([...Object.keys(createHooks()), 'update:modelValue', 'update:nodes', 'update:edges'])
|
||||
|
||||
const modelProps = useVModels(props, emit)
|
||||
|
||||
const { id, hooks, getNodeTypes, getEdgeTypes, ...rest } = useVueFlow()
|
||||
|
||||
useWatch(modelProps, { id, hooks, getNodeTypes, getEdgeTypes, ...rest })
|
||||
|
||||
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())
|
||||
</script>
|
||||
|
||||
@@ -1,76 +1,187 @@
|
||||
import { Ref } from 'vue'
|
||||
import { FlowProps, Store, Node, Edge, Elements } from '~/types'
|
||||
import { Ref, ToRefs } from 'vue'
|
||||
import { WatchPausableReturn } from '@vueuse/core'
|
||||
import { FlowProps, UseVueFlow } from '~/types'
|
||||
|
||||
const isDef = <T>(val: T): val is NonNullable<T> => typeof val !== 'undefined'
|
||||
export default (
|
||||
{
|
||||
modelValue,
|
||||
nodes,
|
||||
edges,
|
||||
}: {
|
||||
modelValue?: Ref<Elements | undefined>
|
||||
nodes?: Ref<Node[] | undefined>
|
||||
edges?: Ref<Edge[] | undefined>
|
||||
},
|
||||
props: FlowProps,
|
||||
store: Store,
|
||||
) => {
|
||||
export default (models: ToRefs<FlowProps>, store: UseVueFlow) => {
|
||||
const scope = getCurrentScope()
|
||||
|
||||
scope?.run(() => {
|
||||
if (isDefined(props.modelValue)) {
|
||||
const { pause, resume } = pausableWatch([() => props.modelValue, () => props.modelValue?.length], async ([v]) => {
|
||||
if (v && Array.isArray(v)) {
|
||||
pause()
|
||||
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()
|
||||
}
|
||||
})
|
||||
}
|
||||
const watchModelValue = () => {
|
||||
scope.run(() => {
|
||||
let pauseModel: WatchPausableReturn
|
||||
let pauseStore: WatchPausableReturn
|
||||
|
||||
watch(
|
||||
() => props.maxZoom,
|
||||
(v) => isDef(v) && store.setMaxZoom(v),
|
||||
{ immediate: isDef(props.maxZoom) },
|
||||
)
|
||||
watch(
|
||||
() => props.minZoom,
|
||||
(v) => isDef(v) && store.setMinZoom(v),
|
||||
{ immediate: isDef(props.minZoom) },
|
||||
)
|
||||
// eslint-disable-next-line prefer-const
|
||||
pauseModel = watchPausable(
|
||||
[models.modelValue, () => models.modelValue?.value?.length],
|
||||
([v]) => {
|
||||
if (v && Array.isArray(v)) {
|
||||
if (pauseStore) pauseStore.pause()
|
||||
if (pauseModel) pauseModel.pause()
|
||||
|
||||
const skip = ['modelValue', 'edges', 'nodes', 'maxZoom', 'minZoom']
|
||||
Object.keys(props).forEach((p) => {
|
||||
if (!skip.includes(p)) {
|
||||
const prop = props[p as keyof typeof props]
|
||||
watch(
|
||||
() => prop,
|
||||
(v) => isDef(v) && ((store as any)[p] = v),
|
||||
{ immediate: isDef(prop) },
|
||||
store.setElements(v)
|
||||
|
||||
pauseStore = watchPausable(
|
||||
[() => store.edges.value.length, () => store.nodes.value.length],
|
||||
() => {
|
||||
models.modelValue!.value = [...store.nodes.value, ...store.edges.value]
|
||||
},
|
||||
{ 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 => ({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
nodeTypes: {},
|
||||
edgeTypes: {},
|
||||
|
||||
initialized: false,
|
||||
instance: null,
|
||||
|
||||
Reference in New Issue
Block a user