This commit is contained in:
Seth Trowbridge 2023-02-09 10:56:25 -05:00
parent f6da848c2b
commit 0e2946e4e6

View File

@ -21,6 +21,7 @@ export const Branch = (props) => {
React.useEffect(() => { React.useEffect(() => {
const [{ Open, Done, Away }] = stageParent; const [{ Open, Done, Away }] = stageParent;
const [, Shut] = stage; const [, Shut] = stage;
if (!Open && Done) { if (!Open && Done) {
Shut({ Open: false, Done: true, Away }); Shut({ Open: false, Done: true, Away });
} }
@ -37,33 +38,12 @@ export const Button = (props) => {
/** @type Binding */ /** @type Binding */
const [stageGet, stageSet] = React.useContext(BranchContext); const [stageGet, stageSet] = React.useContext(BranchContext);
/** @type React.MutableRefObject<Handler|null> */
const refHandler = React.useRef(null);
React.useEffect(() => {
console.log("recreating handler", stageGet);
refHandler.current = (away) => {
console.log("set: away");
stageSet((state) => ({ ...state, Away: away }));
};
}, [stageGet]);
/** @type React.MutableRefObject<HTMLElement|null> */
const refEl = React.useRef(null);
useAway(refEl, refHandler);
const handleClick = () => { const handleClick = () => {
console.log("set: open"); console.log("set: open");
stageSet((state) => ({ ...state, Open: !state.Open, Done: false })); stageSet((state) => ({ ...state, Open: !state.Open, Done: false }));
}; };
React.useEffect(() => { return html`<button onClick=${handleClick} class=${props.class}>stage: ${
console.log("away *changed*");
if (stageGet.Away && stageGet.Open) {
handleClick();
}
}, [stageGet.Away]);
return html`<button ref=${refEl} onClick=${handleClick} class=${props.class}>stage: ${
JSON.stringify(stageGet) JSON.stringify(stageGet)
} }
${props.children} ${props.children}
@ -82,14 +62,20 @@ export const Menu = (props) => {
(refCollapser.current = Collapser(refElement.current)); (refCollapser.current = Collapser(refElement.current));
}, []); }, []);
React.useEffect(() => {
const instant = stageGet.Open == false && stageGet.Done; const instant = stageGet.Open == false && stageGet.Done;
refCollapser.current && refCollapser.current( refCollapser.current && refCollapser.current(
stageGet.Open, stageGet.Open,
instant ? 0 : 600, instant ? 0 : 600,
() => stageSet({ ...stageGet, Done: true }), () => stageSet({ ...stageGet, Done: true }),
); );
}, [stageGet]);
/** @type React.MutableRefObject<Handler|null> */
const refHandler = React.useRef(null);
refHandler.current = (away) => {
console.log("set: away");
stageSet((state) => ({ ...state, Away: away }));
};
useAway(refElement, refHandler);
return html` return html`
<div ref=${refElement} class=${props.class}> <div ref=${refElement} class=${props.class}>
@ -99,68 +85,58 @@ export const Menu = (props) => {
}; };
/** @typedef {(Open:boolean)=>void} UserDone */ /** @typedef {(Open:boolean)=>void} UserDone */
/** @typedef {(inOpen:boolean, inMilliseconds:number, inUserDone:UserDone)=>void} Toggler */ /** @typedef {(inOpen:boolean, inMilliseconds:number, inUserDone:UserDone)=>void | (()=>void) } Toggler */
/** @typedef {(inElement:HTMLElement)=>Toggler} Collapse */ /** @typedef {(inElement:HTMLElement)=>Toggler} Collapse */
/** @type Collapse */ /** @type Collapse */
const Collapser = (inElement) => { const Collapser = (inElement) => {
/** @type UserDone */ /** @type UserDone */
let userDone = () => {}; let userDone = () => {};
let userMode = false; let userMode = false;
const collapse = inElement; /** @type number */ let frameRequest;
/** @type {(inEvent:TransitionEvent)=>void} */ /** @type {(inEvent:TransitionEvent)=>void} */
const done = (inEvent) => { const done = (inEvent) => {
if (inEvent.propertyName == "height" && inEvent.target == collapse) { if (inEvent.propertyName == "height" && inEvent.target == inElement) {
inEvent.stopPropagation(); inEvent.stopPropagation();
if (userMode) { if (userMode) {
collapse.style.height = "auto"; inElement.style.height = "auto";
collapse.style.overflow = "visible"; inElement.style.overflow = "visible";
userDone(true);
} else {
userDone(false);
} }
userDone(userMode);
} }
}; };
inElement.addEventListener("transitionend", done);
/** @type Toggler */ /** @type Toggler */
const show = (inOpen, inMs, inDone) => { const show = (inOpen, inMs, inDone) => {
userMode = inOpen; cancelAnimationFrame(frameRequest);
collapse.removeEventListener("transitionend", done); if ((!inOpen && !inMs) && !inDone) {
inElement.removeEventListener("transitionend", done);
} else {
userDone = inDone; userDone = inDone;
collapse.addEventListener("transitionend", done); userMode = inOpen;
inElement.style.height = inElement.clientHeight + "px";
collapse.style.height = collapse.clientHeight + "px"; inElement.style.overflow = "hidden";
collapse.style.overflow = "hidden"; inElement.style.transition = "none";
collapse.style.transition = "none"; frameRequest = requestAnimationFrame(() => {
requestAnimationFrame(() => { inElement.style.height = `${inOpen ? inElement.scrollHeight : 0}px`;
collapse.style.height = `${inOpen ? collapse.scrollHeight : 0}px`; inElement.style.transition = `height ${inMs / 1000}s`;
collapse.style.transition = `height ${inMs / 1000}s`;
}); });
}
}; };
return show; return show;
}; };
/** @typedef {(away:boolean, e:Event)=>void} Handler */ /** @typedef {(away:boolean, e:Event)=>void} Handler */
/** @type {(inRef:React.MutableRefObject<HTMLElement|null>, inHandler:React.MutableRefObject<Handler|null>)=>void} */ /** @type {(inRef:React.MutableRefObject<HTMLElement|null>, inHandler:React.MutableRefObject<Handler|null>)=>void} */
export const useAway = (inRef, inHandler) => { export const useAway = (inRef, inHandlerRef) => {
/** @type React.MutableRefObject<(e:Event)=>void> */ /** @type React.MutableRefObject<(e:Event)=>void> */
const handlerClick = React.useRef((e) => { const handlerClick = React.useRef((e) => {
const index = inRef.current ? e.composedPath().indexOf(inRef.current) : -1; const index = inRef.current ? e.composedPath().indexOf(inRef.current) : -1;
inHandler.current && inHandler.current(index < 0, e); inHandlerRef.current && inHandlerRef.current(index < 0, e);
}); });
React.useEffect(() => { React.useEffect(() => {
document.addEventListener("click", handlerClick.current); document.addEventListener("click", handlerClick.current);
return () => document.removeEventListener("click", handlerClick.current); return () => document.removeEventListener("click", handlerClick.current);
}); }, []);
};
const GlobalClick = {
Bind: () => {
if (!GlobalClick.Live) {
GlobalClick.Live = true;
document.addEventListener("click", (inEvent) => {
GlobalClick.Path = inEvent.composedPath();
});
}
},
Live: false,
Path: [],
}; };