2026-03-22 04:08:56 +02:00
|
|
|
import { createContext, useCallback, useContext, useRef, useState } from "react";
|
|
|
|
|
import type { ReactNode } from "react";
|
2026-03-22 13:25:49 +02:00
|
|
|
import { cloneWithoutAnimations } from "./Liftable";
|
2026-03-22 04:08:56 +02:00
|
|
|
|
|
|
|
|
type LiftContextValue = {
|
|
|
|
|
liftedIds: Set<string>;
|
|
|
|
|
alwaysLiftedIds: Set<string>;
|
2026-03-22 13:25:49 +02:00
|
|
|
modalOpen: boolean;
|
2026-03-22 04:08:56 +02:00
|
|
|
setLiftedIds: (ids: string[]) => void;
|
|
|
|
|
registerAlways: (id: string) => void;
|
|
|
|
|
unregisterAlways: (id: string) => void;
|
2026-03-22 13:25:49 +02:00
|
|
|
registerModal: () => void;
|
|
|
|
|
beginModalClose: () => void;
|
|
|
|
|
endModalClose: () => void;
|
2026-03-22 04:08:56 +02:00
|
|
|
portalContainer: HTMLElement | null;
|
|
|
|
|
setPortalContainer: (el: HTMLElement | null) => void;
|
2026-03-22 13:25:49 +02:00
|
|
|
setCloneContainer: (el: HTMLElement | null) => void;
|
2026-03-22 04:08:56 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const LiftContext = createContext<LiftContextValue | null>(null);
|
|
|
|
|
|
|
|
|
|
export function useLift(): LiftContextValue {
|
|
|
|
|
const ctx = useContext(LiftContext);
|
|
|
|
|
if (!ctx) throw new Error("useLift must be used within LiftProvider");
|
|
|
|
|
return ctx;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function LiftProvider({ children }: { children: ReactNode }) {
|
|
|
|
|
const [liftedIds, setLiftedIdsRaw] = useState<Set<string>>(new Set());
|
|
|
|
|
const [alwaysLiftedIds, setAlwaysLiftedIds] = useState<Set<string>>(new Set());
|
2026-03-22 13:25:49 +02:00
|
|
|
const [modalOpen, setModalOpen] = useState(false);
|
2026-03-22 04:08:56 +02:00
|
|
|
const [portalContainer, setPortalContainer] = useState<HTMLElement | null>(null);
|
|
|
|
|
const alwaysRef = useRef<Set<string>>(new Set());
|
2026-03-22 13:25:49 +02:00
|
|
|
const modalCountRef = useRef(0);
|
|
|
|
|
const cloneContainerRef = useRef<HTMLElement | null>(null);
|
2026-03-22 04:08:56 +02:00
|
|
|
|
|
|
|
|
const setLiftedIds = useCallback((ids: string[]) => {
|
|
|
|
|
setLiftedIdsRaw(new Set(ids));
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-03-22 13:25:49 +02:00
|
|
|
const registerModal = useCallback(() => {
|
|
|
|
|
modalCountRef.current++;
|
|
|
|
|
setModalOpen(true);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const beginModalClose = useCallback(() => {
|
|
|
|
|
if (portalContainer && cloneContainerRef.current) {
|
|
|
|
|
cloneWithoutAnimations(portalContainer, cloneContainerRef.current);
|
|
|
|
|
}
|
|
|
|
|
modalCountRef.current--;
|
|
|
|
|
if (modalCountRef.current === 0) {
|
|
|
|
|
setModalOpen(false);
|
|
|
|
|
}
|
|
|
|
|
}, [portalContainer]);
|
|
|
|
|
|
|
|
|
|
const endModalClose = useCallback(() => {
|
|
|
|
|
if (cloneContainerRef.current) {
|
|
|
|
|
cloneContainerRef.current.innerHTML = "";
|
|
|
|
|
}
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const setCloneContainer = useCallback((el: HTMLElement | null) => {
|
|
|
|
|
cloneContainerRef.current = el;
|
|
|
|
|
}, []);
|
|
|
|
|
|
2026-03-22 04:08:56 +02:00
|
|
|
const registerAlways = useCallback((id: string) => {
|
|
|
|
|
alwaysRef.current.add(id);
|
|
|
|
|
setAlwaysLiftedIds(new Set(alwaysRef.current));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const unregisterAlways = useCallback((id: string) => {
|
|
|
|
|
alwaysRef.current.delete(id);
|
|
|
|
|
setAlwaysLiftedIds(new Set(alwaysRef.current));
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<LiftContext
|
|
|
|
|
value={{
|
|
|
|
|
liftedIds,
|
|
|
|
|
alwaysLiftedIds,
|
2026-03-22 13:25:49 +02:00
|
|
|
modalOpen,
|
2026-03-22 04:08:56 +02:00
|
|
|
setLiftedIds,
|
|
|
|
|
registerAlways,
|
|
|
|
|
unregisterAlways,
|
2026-03-22 13:25:49 +02:00
|
|
|
registerModal,
|
|
|
|
|
beginModalClose,
|
|
|
|
|
endModalClose,
|
2026-03-22 04:08:56 +02:00
|
|
|
portalContainer,
|
|
|
|
|
setPortalContainer,
|
2026-03-22 13:25:49 +02:00
|
|
|
setCloneContainer,
|
2026-03-22 04:08:56 +02:00
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{children}
|
|
|
|
|
</LiftContext>
|
|
|
|
|
);
|
|
|
|
|
}
|