diff --git a/src/react-components/Anchor.css b/src/react-components/Anchor.css index 0f1a7b5b..c7d331ae 100644 --- a/src/react-components/Anchor.css +++ b/src/react-components/Anchor.css @@ -20,8 +20,8 @@ } .graph-block-anchor.graph-block-position-absolute { - --x: var(--graph-block-anchor-x, 0px); - --y: var(--graph-block-anchor-y, 0px); + --x: var(--graph-block-anchor-x, -99999px); + --y: var(--graph-block-anchor-y, -99999px); --x-offset: calc(var(--width) / 2); --y-offset: calc(var(--height) / 2); diff --git a/src/react-components/hooks/useBlockAnchorState.ts b/src/react-components/hooks/useBlockAnchorState.ts index 37f87db5..22cf31e9 100644 --- a/src/react-components/hooks/useBlockAnchorState.ts +++ b/src/react-components/hooks/useBlockAnchorState.ts @@ -3,7 +3,7 @@ import { Graph } from "../../graph"; import { AnchorState } from "../../store/anchor/Anchor"; import { useBlockState } from "./useBlockState"; -import { useComputedSignal, useSignalEffect } from "./useSignal"; +import { useComputedSignal, useSignalLayoutEffect } from "./useSignal"; export function useBlockAnchorState(graph: Graph, anchor: TAnchor): AnchorState | undefined { const blockState = useBlockState(graph, anchor.blockId); @@ -19,7 +19,7 @@ export function useBlockAnchorPosition( state: AnchorState | undefined, anchorContainerRef: React.MutableRefObject | undefined ) { - useSignalEffect(() => { + useSignalLayoutEffect(() => { if (!state || !anchorContainerRef?.current) { return; } diff --git a/src/react-components/hooks/useSignal.ts b/src/react-components/hooks/useSignal.ts index 6cb21c2f..20f02a2d 100644 --- a/src/react-components/hooks/useSignal.ts +++ b/src/react-components/hooks/useSignal.ts @@ -1,4 +1,4 @@ -import { DependencyList, useCallback, useEffect, useMemo, useSyncExternalStore } from "react"; +import { DependencyList, useCallback, useEffect, useLayoutEffect, useMemo, useSyncExternalStore } from "react"; import { computed, effect } from "@preact/signals-core"; import type { Signal } from "@preact/signals-core"; @@ -65,3 +65,21 @@ export function useSignalEffect(effectFn: () => void, deps: DependencyList) { return effect(() => handle()); }, deps); } + +/** + * Like useSignalEffect but runs synchronously after DOM mutations, before the browser paints. + * Use when signal changes must be reflected in the DOM without a visible frame delay. + * + * @example + * ```tsx + * useSignalLayoutEffect(() => { + * ref.current?.style.setProperty("--x", `${signal.value}px`); + * }, [signal]); + * ``` + */ +export function useSignalLayoutEffect(effectFn: () => void, deps: DependencyList) { + const handle = useFn(effectFn); + useLayoutEffect(() => { + return effect(() => handle()); + }, deps); +}