import { App, Astal, Gdk, Gtk } from "astal/gtk3" import GLib from "gi://GLib" import { Variable, bind } from "astal" const DURATION = 180 interface DropdownProps { gdkmonitor: Gdk.Monitor target: Gtk.Widget open: Variable width?: number vertical?: boolean className?: string children: Array } export default function Dropdown({ gdkmonitor, target, open, width = 260, vertical = true, className = "", children, }: DropdownProps) { const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor const GAP = 8 const child = ( {children} ) function reposition() { const toplevel = target.get_toplevel() const [, tx, ty] = target.translate_coordinates(toplevel, 0, 0) const { width: bw, height: bh } = target.get_allocation() const { width: sw } = gdkmonitor.get_geometry() const x = Math.min(Math.max(tx + bw / 2 - width / 2, GAP), sw - width - GAP) const y = ty + bh + GAP child.marginStart = Math.round(x) child.marginTop = Math.round(y) } // the window outlives the animation: it hides once the slide-out // revealer finished, and hiding the window resolves to open=false const winVisible = Variable(false) let closeTimer: number | undefined open.subscribe((v) => { if (closeTimer !== undefined) { GLib.source_remove(closeTimer) closeTimer = undefined } if (v) { reposition() winVisible.set(true) } else if (winVisible.get()) { closeTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, DURATION + 20, () => { winVisible.set(false) closeTimer = undefined return GLib.SOURCE_REMOVE }) } }) return ( { if (!self.visible && open.get()) open.set(false) }} onButtonPressEvent={(self, e) => { const event = e as unknown as Gdk.Event const [, px, py] = event.get_coords() const { x, y, width: w, height: h } = child.get_allocation() const xOut = px < x || px > x + w const yOut = py < y || py > y + h if (xOut || yOut) open.set(false) }} onKeyPressEvent={(_, e) => { const event = e as unknown as Gdk.Event if (event.get_keyval()[1] === Gdk.KEY_Escape) open.set(false) }} > {child} ) }