diff --git a/examples/server/public/index.html b/examples/server/public/index.html index fa5d3632a..c6d674e16 100644 --- a/examples/server/public/index.html +++ b/examples/server/public/index.html @@ -428,6 +428,121 @@ ` } + // simple popover impl + const Popover = (props) => { + const isOpen = useSignal(false); + const position = useSignal({ top: '0px', left: '0px' }); + const buttonRef = useRef(null); + const popoverRef = useRef(null); + + const togglePopover = () => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); + position.value = { + top: `${rect.bottom + window.scrollY}px`, + left: `${rect.left + window.scrollX}px`, + }; + } + isOpen.value = !isOpen.value; + }; + + const handleClickOutside = (event) => { + if (popoverRef.current && !popoverRef.current.contains(event.target) && !buttonRef.current.contains(event.target)) { + isOpen.value = false; + } + }; + + useEffect(() => { + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, []); + + return html` + ${props.children} + ${isOpen.value && html` + <${Portal} into="#portal"> +
+ ${props.popoverChildren} +
+ + `} + `; + }; + + // Source: preact-portal (https://github.com/developit/preact-portal/blob/master/src/preact-portal.js) + /** Redirect rendering of descendants into the given CSS selector */ + class Portal extends Component { + componentDidUpdate(props) { + for (let i in props) { + if (props[i] !== this.props[i]) { + return setTimeout(this.renderLayer); + } + } + } + + componentDidMount() { + this.isMounted = true; + this.renderLayer = this.renderLayer.bind(this); + this.renderLayer(); + } + + componentWillUnmount() { + this.renderLayer(false); + this.isMounted = false; + if (this.remote && this.remote.parentNode) this.remote.parentNode.removeChild(this.remote); + } + + findNode(node) { + return typeof node === 'string' ? document.querySelector(node) : node; + } + + renderLayer(show = true) { + if (!this.isMounted) return; + + // clean up old node if moving bases: + if (this.props.into !== this.intoPointer) { + this.intoPointer = this.props.into; + if (this.into && this.remote) { + this.remote = render(html`<${PortalProxy} />`, this.into, this.remote); + } + this.into = this.findNode(this.props.into); + } + + this.remote = render(html` + <${PortalProxy} context=${this.context}> + ${show && this.props.children || null} + + `, this.into, this.remote); + } + + render() { + return null; + } + } + // high-order component that renders its first child if it exists. + // used as a conditional rendering proxy. + class PortalProxy extends Component { + getChildContext() { + return this.props.context; + } + render({ children }) { + return children || null; + } + } + function App(props) { return html`