import { html } from "htm"; import * as React from "preact"; /** @typedef {({children, classes}:{children:preact.VNode, classes:string|null})=>preact.VNode} BasicElement */ /** @typedef {{Open:boolean, Done:boolean}} Stage*/ /** @typedef {[set:Stage, get:React.StateUpdater]} Binding */ const BranchContext = React.createContext( /** @type Binding */ ([{ Open: false, Done: true }, (_n) => {}]), ); /** @type BasicElement */ export const Branch = (props) => { /** @type Binding */ const stage = React.useState( /** @type Stage */ ({ Open: false, Done: true }), ); /** @type Binding */ const stageParent = React.useContext(BranchContext); React.useEffect(() => { const [{ Open, Done }, Shut] = stageParent; if (!Open && Done) { Shut({ Open: false, Done: true }); } }, [stageParent]); return React.createElement(BranchContext.Provider, { value: stage, children: props.children, }); }; /** @type BasicElement */ export const Button = (props) => { /** @type Binding */ const [stageGet, stageSet] = React.useContext(BranchContext); const handler = () => { const value = { Open: !stageGet.Open, Done: false }; stageSet(value); console.log(value); }; return html``; }; /** @type BasicElement */ export const Menu = (props) => { const [stageGet, stageSet] = React.useContext(BranchContext); const refElement = React.useRef(null); const refCollapser = React.useRef(null); React.useEffect(() => { refCollapser.current = Collapser(refElement.current); }, []); React.useEffect(() => { refCollapser.current( stageGet.Open, stageGet.Open == false && stageGet.Done ? 0 : 600, () => stageSet({ ...stageGet, Done: true }), ); }, [stageGet.Open]); return html`
${props.children}
`; }; /** @typedef {(Open:boolean)=>void} UserDone */ /** @typedef {(inOpen:boolean, inMilliseconds:number, inUserDone:UserDone)=>void} Toggler */ /** @type {(inElement:HTMLElement)=>Toggler} */ const Collapser = (inElement) => { console.log("Collapser initialized"); /** @type UserDone */ let userDone = () => {}; const collapse = inElement; /** @type {(inEvent:TransitionEvent)=>void} */ const done = (inEvent) => { if (inEvent.propertyName == "height" && inEvent.target == collapse) { inEvent.stopPropagation(); if (collapse.clientHeight > 0) { collapse.style.height = "auto"; collapse.style.overflow = "visible"; userDone(true); } else { userDone(false); } } }; /** @type Toggler */ const show = (inOpen, inMs, inDone) => { collapse.removeEventListener("transitionend", done); userDone = inDone; collapse.addEventListener("transitionend", done); collapse.style.height = collapse.clientHeight + "px"; collapse.style.overflow = "hidden"; collapse.style.transition = "none"; requestAnimationFrame(() => { collapse.style.height = `${inOpen ? collapse.scrollHeight : 0}px`; collapse.style.transition = `height ${inMs / 1000}s`; }); }; return show; };