Guppy keeps overlay state Elixir-owned. Native code renders the current IR and emits events; it does not retain open/closed truth beyond GPUI focus/layout state.
Ownership and lifecycle
popoverandselectopenflags are owned by the process that renders the containing window.- Trigger clicks/keyboard toggles emit the configured
clickcallback; the owner updatesopenand rerenders. - Close requests emit the configured
closecallback:selectemits close on Escape while open and on mouse down outside the list.popoveremits close on Escape while open and, whenclose_on_click_outside: true, on mouse down outside the popover content.
- Selecting a
selectoption emitschangewith the selected value. The owner decides whether that also closes the select. - Focus return is explicit for popup-window overlays such as
Guppy.App.open_context_menu/3; by default app context menus return focus to the source app window, or toreturn_focus_to: window_idwhen provided. - Inline IR overlays (
popover/select) are pruned with their owning window IR. If the owning window process exits,Guppy.Servercloses the native window and native retained state is pruned with the view.
Keyboard behavior
popovertriggers are focusable and treat Enter/Space as toggle requests.- Open
popovertriggers treat Escape as a close request. selecttriggers are focusable and support:- Enter/Space: toggle request via
click - Up/Down: previous/next enabled option via
change - Home/End: first/last enabled option via
change - single-character typeahead: next enabled option whose label starts with that character, wrapping after the current value
- Escape while open: close request via
close
- Enter/Space: toggle request via
- Disabled
selectoptions are skipped by keyboard navigation/typeahead and do not emit click changes.
Positioning
popover exposes GPUI anchored positioning through typed IR fields:
anchor::top_left | :top_right | :bottom_left | :bottom_rightanchor_position: explicit{x, y}point, when neededanchor_position_mode::window | :localanchor_offset:{x, y}offset from the anchor pointanchor_fit::switch_anchor | :snap_to_window | :snap_to_window_with_marginsnap_margin: non-negative logical pixels for margin-based window-edge snappingstack_priority: deferred-layer priority
select exposes the same practical window-edge controls for its list overlay: anchor, anchor_offset, anchor_fit, and snap_margin. It always positions locally relative to the trigger. Defaults keep prior behavior: anchor: :top_left, anchor_offset: {0, 32}, anchor_fit: :snap_to_window_with_margin, and snap_margin: 8.
Scroll-parent behavior is intentionally bounded to GPUI's anchored local/window coordinate modes. If a dropdown must ignore a scrolling parent, render a popup-window overlay from Elixir instead of nesting another native overlay.
Nested overlays
Nested native overlays are deliberately unsupported today. popover children may contain nested ordinary panels/layout, but validation and native decode reject nested popover or select nodes anywhere inside popover content. This avoids ambiguous focus return, Escape routing, and z-order ownership until a complete overlay stack model is designed.
Use popup windows (Guppy.App.open_context_menu/3 or an app-owned transient window) for multi-level menu flows that need separate ownership and focus return.