Collapser

This commit is contained in:
Seth Trowbridge 2023-01-24 10:13:43 -05:00
parent aa1a821919
commit d77f200521
2 changed files with 90 additions and 11 deletions

View File

@ -91,6 +91,13 @@ export default () => {
<${PersonTable}/> <${PersonTable}/>
<${Tree.Branch}> <${Tree.Branch}>
<${Tree.Button}><//> <${Tree.Button}><//>
<${Tree.Menu}>
<div class=${"p-4"}>
<p>
The method addEventListener() works by adding a function, or an object that implements EventListener, to the list of event listeners for the specified event type on the EventTarget on which it's called. If the function or object is already in the list of event listeners for this target, the function or object is not added a second time.
</p>
</div>
<//>
<//> <//>
</div> </div>
</div>`; </div>`;

View File

@ -1,37 +1,109 @@
import { html } from "htm"; import { html } from "htm";
import * as React from "preact"; import * as React from "preact";
/** @typedef {0|0.5|1|1.5|2} Stage*/ /** @typedef {({children, classes}:{children:preact.VNode, classes:string|null})=>preact.VNode} BasicElement */
/** @typedef {{Open:boolean, Done:boolean}} Stage*/
/** @typedef {[set:Stage, get:React.StateUpdater<Stage>]} Binding */ /** @typedef {[set:Stage, get:React.StateUpdater<Stage>]} Binding */
const BranchContext = React.createContext( const BranchContext = React.createContext(
/** @type Binding */ ([0, (_n) => {}]), /** @type Binding */ ([{ Open: false, Done: true }, (_n) => {}]),
); );
/** @type {({children}:{children:preact.VNode})=>preact.VNode} */ /** @type BasicElement */
export const Branch = (props) => { export const Branch = (props) => {
/** @type Binding */ const stage = React.useState(/** @type Stage */ (0)); /** @type Binding */ const stage = React.useState(
/** @type Stage */ ({ Open: false, Done: true }),
);
/** @type Binding */ const stageParent = React.useContext(BranchContext); /** @type Binding */ const stageParent = React.useContext(BranchContext);
React.useEffect(() => { React.useEffect(() => {
if (stageParent[0] == 2) { const [{ Open, Done }, Shut] = stageParent;
stage[1](0); if (!Open && Done) {
Shut({ Open: false, Done: true });
} }
}, [stageParent]); }, [stageParent]);
return html`<${BranchContext.Provider} value=${stage}>${props.children}<//>`; return React.createElement(BranchContext.Provider, {
value: stage,
children: props.children,
});
}; };
export const Button = () => { /** @type BasicElement */
export const Button = (props) => {
/** @type Binding */ /** @type Binding */
const [stageGet, stageSet] = React.useContext(BranchContext); const [stageGet, stageSet] = React.useContext(BranchContext);
const handler = () => { const handler = () => {
let value = stageGet + 0.5; const value = { Open: !stageGet.Open, Done: false };
if (value > 2) value = 0;
stageSet(value); stageSet(value);
console.log(value); console.log(value);
}; };
return html`<button onClick=${handler}>stage: ${stageGet}</button>`; return html`<button onClick=${handler} class=${props.classes}>stage: ${
JSON.stringify(stageGet)
}
${props.children}
</button>`;
};
/** @type BasicElement */
export const Menu = (props) => {
const [stageGet, stageSet] = React.useContext(BranchContext);
const refElement = React.useRef(null);
React.useEffect(() => {
console.log(`menu should change`, stageGet);
Collapser(refElement.current, () => {
stageSet({ ...stageGet, Done: true });
})(stageGet.Open, stageGet.Open == false && stageGet.Done ? 0 : 600);
}, [stageGet.Open]);
return html`
<div ref=${refElement} class=${props.classes}>
${props.children}
</div>
`;
};
/** @type BasicElement */
export const Collapse = (props) => {
const ref = React.useRef();
const style = { overflow: "visible", height: "auto" };
return html`<div ref=${ref} style=${style}>${props.children}</div>`;
};
/** @typedef {(inOpen:boolean, inMilliseconds:number)=>void} Toggler */
/** @type {(inElement:HTMLElement, inUserDone:(Open:boolean)=>void)=>Toggler} */
const Collapser = (inElement, inUserDone) => {
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";
inUserDone(true);
} else {
inUserDone(false);
}
}
};
/** @type Toggler */
const show = (inOpen, inMs) => {
collapse.removeEventListener("transitionend", done);
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;
}; };