import React from "react"; export default (props:{open:boolean, children:React.JSX.Element|React.JSX.Element[]})=> { const [openGet, openSet] = React.useState(props.open); const [doneGet, doneSet] = React.useState(true); const refEl:React.RefObject = React.useRef(null); const refCollapse:React.RefObject = React.useRef(null); React.useEffect(()=> { if(refEl.current) { refCollapse.current = Collapser(refEl.current, openGet); } return ()=> refCollapse.current && refCollapse.current(); } , []); const Handler =()=> { openSet((old)=> { if(refCollapse.current) { doneSet(false); refCollapse.current(!old, 1000, ()=>doneSet(true)); } return !old; }); }; return
}> {!(!openGet && doneGet) && props.children}
}; export function useAway(inRef:React.Ref, inHandler:(inAway:boolean)=>void) { const handlerRef = React.useRef(inHandler); handlerRef.current = inHandler; React.useEffect(()=> { const iterate = (element:EventTarget)=>element === inRef.current; const handler = (event:MouseEvent)=> handlerRef.current( event.composedPath().find(iterate) ? false : true ); document.body.addEventListener("click", handler); return ()=> document.body.removeEventListener("click", handler); } , []); } type CollapseStateGet = {Done:boolean, Open:boolean, Away:boolean}; type CollapseStateSet = (args:{Done?:boolean, Open?:boolean, Away?:boolean})=>void; type CollapseStateBinding = [state:CollapseStateGet, update:CollapseStateSet]; const CollapseContext = React.createContext([{Done:true, Open:true, Away:false}, (args)=>{}] as CollapseStateBinding); export const CollapseGroup =({children}:{children:React.JSX.Element|React.JSX.Element[]})=> { const [stateGet, stateSet] = React.useState({Done:true, Open:false, Away:false} as CollapseStateGet); const setAny:CollapseStateSet =(params)=> stateSet((old)=> ({...old, ...params})); React.useEffect(()=>{stateGet.Away && setAny({Open:false})}, [stateGet.Away]); return {children}; }; export const CollapseButton =()=> { const [stateGet, stateSet] = React.useContext(CollapseContext); const buttonRef:React.Ref = React.useRef(null); useAway(buttonRef, (Away)=>stateSet({Away})); return } type DoneCallback =(openState:boolean)=>void; export type CollapseControls =(inOpen?:boolean, inMs?:number, inDone?:DoneCallback)=>void; export function Collapser(inElement:HTMLElement, initialState = false) { let userDone:DoneCallback = (openState) => {}; let userMode = initialState; let frameRequest = 0; const done = (inEvent:TransitionEvent)=> { if (inEvent.propertyName == "height" && inEvent.target == inElement) { inEvent.stopPropagation(); if (userMode) { inElement.style.height = "auto"; inElement.style.overflow = "visible"; } console.log(" -- anim stop --") userDone(userMode); } }; inElement.addEventListener("transitionend", done); return function(inOpen?:boolean, inMs?:number, inDone?:DoneCallback) { cancelAnimationFrame(frameRequest); if(arguments.length) { console.log(" -- anim start --", inOpen); userDone = inDone|| ((m)=>{}) as DoneCallback; userMode = inOpen === true; inElement.style.height = inElement.clientHeight + "px"; if(inOpen === false) { console.log(" --height check", inElement.style.height) } inElement.style.overflow = "hidden"; inElement.style.transition = "none"; frameRequest = requestAnimationFrame(()=> { inElement.style.transition = `height ${(inMs||1000) / 1000}s`; frameRequest = requestAnimationFrame(()=> { inElement.style.height = `${inOpen ? inElement.scrollHeight : 0}px`; }); }); } else { inElement.removeEventListener("transitionend", done); } } as CollapseControls; }