package webui import "kjol/vdom" // Port of web/kit/Floating.tsx. // // The TSX is a floating-ui-style popover kit built on Solid context, refs, // getBoundingClientRect, requestAnimationFrame, window scroll/resize listeners, // document-level mousedown/keydown handlers, a Portal to document.body, and a // global single-open FloatingManager. None of that has an equivalent in the // neutral vdom runtime, so this port keeps the API shape (Root / Trigger / // Content) plus the Tailwind, and models open state as a plain value + callback. // // NOTE: All computed positioning (calculatePosition, flip, shift, offset px, // getBoundingClientRect, the position() signal, window scroll/resize reflow) is // dropped. FloatingContent is approximated with a statically-positioned // `absolute` element anchored to FloatingRoot's `relative` container, placed via // Tailwind utilities chosen from the Placement value. The original rendered the // content through a Portal with `position: fixed`; here it stays in-flow. // NOTE: The global single-open FloatingManager, the open-order stack // (openFloatings), outside-click dismissal, Escape-to-close, and hover-open / // hover-close timers are dropped — callers own open state and decide when to // toggle it. useFloatingContext / FloatingContextValue and the useFloatingHover // hook are dropped (no Solid context; nothing to share through). // Placement values (subset of CSS anchor placements) understood by // FloatingContent's static positioning approximation. const ( PlacementTop = "top" PlacementTopStart = "top-start" PlacementTopEnd = "top-end" PlacementBottom = "bottom" PlacementBottomStart = "bottom-start" PlacementBottomEnd = "bottom-end" PlacementLeft = "left" PlacementLeftStart = "left-start" PlacementLeftEnd = "left-end" PlacementRight = "right" PlacementRightStart = "right-start" PlacementRightEnd = "right-end" ) // PositionOptions mirrors the TSX PositionOptions. Retained for API parity; in // this port only Placement influences rendering (see the file-level NOTE) — the // numeric/flip/shift fields are not consumed because there is no measurement. type PositionOptions struct { Placement string Offset int Flip bool Shift bool ShiftPadding int } // floatingPlacementClass maps a Placement to Tailwind utilities that position an // `absolute` child relative to its `relative` FloatingRoot container. The ~4px // default offset is approximated with the mt-1/mb-1/ml-1/mr-1 gap classes. func floatingPlacementClass(placement string) string { switch placement { case PlacementTop: return "bottom-full left-1/2 -translate-x-1/2 mb-1" case PlacementTopStart: return "bottom-full left-0 mb-1" case PlacementTopEnd: return "bottom-full right-0 mb-1" case PlacementBottom: return "top-full left-1/2 -translate-x-1/2 mt-1" case PlacementBottomEnd: return "top-full right-0 mt-1" case PlacementLeft: return "right-full top-1/2 -translate-y-1/2 mr-1" case PlacementLeftStart: return "right-full top-0 mr-1" case PlacementLeftEnd: return "right-full bottom-0 mr-1" case PlacementRight: return "left-full top-1/2 -translate-y-1/2 ml-1" case PlacementRightStart: return "left-full top-0 ml-1" case PlacementRightEnd: return "left-full bottom-0 ml-1" default: // PlacementBottomStart and unknown values return "top-full left-0 mt-1" } } // FloatingRootProps configures FloatingRoot. Open is the current open state // (caller-held); OnOpenChange is invoked by children that toggle it. The // numeric/flip/shift/standalone fields are retained for API parity but are not // used by the static-positioning approximation (see the file-level NOTE). type FloatingRootProps struct { Open bool OnOpenChange func(bool) Placement string Offset int Flip bool Shift bool ShiftPadding int Standalone bool Class string } // FloatingRoot wraps a Trigger + Content pair. The TSX component rendered no DOM // node (only a context provider); this port emits a `relative inline-block` // container so the absolutely-positioned FloatingContent has an anchor. func FloatingRoot(p FloatingRootProps, children ...*vdom.VNode) *vdom.VNode { return vdom.El("div", kids([]vdom.Mod{ vdom.Attr("class", cx("relative inline-block", p.Class)), }, children)...) } // FloatingTriggerProps configures FloatingTrigger. Open feeds aria-expanded; // OnToggle fires on click. OpenOnHover/HoverDelay/HoverCloseDelay are retained // for API parity but are inert here (hover timers dropped). type FloatingTriggerProps struct { Open bool OnToggle func() OpenOnHover bool HoverDelay int HoverCloseDelay int Class string Title string } // FloatingTrigger renders the