Collapser
This commit is contained in:
parent
aa1a821919
commit
d77f200521
@ -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>`;
|
||||||
|
94
js/tree.js
94
js/tree.js
@ -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;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user