import {startTransition, useEffect, useRef, useState} from 'react' import {useLocation, useNavigate, useRevalidator} from 'react-router' import {type HistoryAdapterNavigate, type HistoryRefresh, type VisualEditingOptions} from '../types' import {enableVisualEditing} from '../ui/enableVisualEditing' /** * @public */ export interface VisualEditingProps extends Omit { /** * @deprecated The history adapter is already implemented */ history?: never /** * The refresh API allows smarter refresh logic than the default `location.reload()` behavior. * You can call the refreshDefault argument to trigger the default refresh behavior so you don't have to reimplement it. */ refresh?: ( payload: HistoryRefresh, refreshDefault: () => false | Promise, ) => false | Promise } export default function VisualEditingComponent(props: VisualEditingProps): null { const {components, refresh, zIndex, onPerspectiveChange} = props const navigateRemix = useNavigate() const navigateRemixRef = useRef(navigateRemix) const [navigate, setNavigate] = useState() const revalidator = useRevalidator() const [revalidatorPromise, setRevalidatorPromise] = useState<(() => void) | null>(null) const [revalidatorLoading, setRevalidatorLoading] = useState(false) useEffect(() => { navigateRemixRef.current = navigateRemix }, [navigateRemix]) useEffect(() => { if (revalidatorPromise && revalidator.state === 'loading') { startTransition(() => setRevalidatorLoading(true)) } else if (revalidatorPromise && revalidatorLoading && revalidator.state === 'idle') { revalidatorPromise() startTransition(() => { setRevalidatorPromise(null) setRevalidatorLoading(false) }) } }, [revalidatorLoading, revalidator.state, revalidatorPromise]) useEffect(() => { const disable = enableVisualEditing({ components, zIndex, onPerspectiveChange, refresh: (payload) => { function refreshDefault() { if (payload.source === 'mutation' && payload.livePreviewEnabled) { return false } return new Promise((resolve) => { revalidator.revalidate() setRevalidatorPromise(() => resolve) }) } return refresh ? refresh(payload, refreshDefault) : refreshDefault() }, history: { subscribe: (_navigate) => { setNavigate(() => _navigate) return () => setNavigate(undefined) }, update: (update) => { if (update.type === 'push' || update.type === 'replace') { navigateRemixRef.current(update.url, { replace: update.type === 'replace', }) } else if (update.type === 'pop') { navigateRemixRef.current(-1) } }, }, }) return () => disable() }, [components, refresh, revalidator, zIndex, onPerspectiveChange]) const location = useLocation() useEffect(() => { if (navigate) { navigate({ type: 'push', url: `${location.pathname}${location.search}${location.hash}`, }) } }, [location.hash, location.pathname, location.search, navigate]) return null }