Port · Calendar: a real exit clears the preview after a key

The calendar's grid clears its preview on leave, and ignores a leave
that follows a key. gpui records the exit a key makes before the
handler runs, so the guard swallowed it and a real exit afterwards
had no change left to report: the stale day stayed the preview. The
grid now listens independently of input, while its days keep the
default, so a key is no leave and a real exit is. A test leaves the
grid after an arrow, a letter and a month key.

shaped()'s doc goes back to its one line; AGENTS.md keeps the why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-09-28 20:54:25 -04:00
1 parent 94deeaed52
commit f76c3c1280
5 files changed
+28 -5

No files matched your search

+1 -1
View File
@@ -53,7 +53,7 @@ Ely GPUI Component. A component library for GPUI, in light and dark.
- `hover()` needs an element id.
- gpui applies hover and group-hover styles once an element has a hitbox, in prepaint and paint, but text takes its color at layout, so a hover cannot recolor text. What shows on hover rests `invisible()` and turns `visible()` through `group_hover` (`chat::ConversationItem`, `messaging::ChatMessage`).
- gpui 0.2.2 panics on a deferred draw inside another. Whatever draws over the page goes through `primitives::raise`: at the top it defers; inside something raised it lays out on its own and draws last there, so a list opened in a dialog lies over the dialog. A raise takes its owner's id and draws under it, so its keyed state and focus hold while other raises come and go. `scripts/check.sh` refuses gpui's `deferred` anywhere else.
- gpui hides hover after a key press until the mouse moves. A hover that holds or shows something (the toast stack, a carousel, tooltips, hover cards, charts, scroll activity) sets `HoverListenerMode::InputModalityIndependent`, so typing under a resting pointer keeps it. A hover preview the keyboard also moves keeps the default, and its leave ignores a key press through `last_input_was_keyboard` (`forms::Calendar`).
- gpui hides hover after a key press until the mouse moves. A hover that holds or shows something (the toast stack, a carousel, tooltips, hover cards, charts, scroll activity) sets `HoverListenerMode::InputModalityIndependent`, so typing under a resting pointer keeps it. A hover preview the keyboard also moves keeps the default on its items; the box that clears it on leave listens independently, so a real exit still reports, and ignores a leave that follows a key through `last_input_was_keyboard` (`forms::Calendar`).
- `occlude()` blocks the pointer for everything painted before it, its own ancestors too. Put it on the outermost box that should stop the pointer, as the toast stack does, or the ancestors' hover never fires.
- A drag that pans, turns or slides goes through gpui's drag, whose moves reach the owner in or out of its box; `on_mouse_move` stops at the box's edge. gpui sends a drag's moves to every listener of its type, so each payload carries its owner's `EntityId` and handlers check it.
- gpui 0.2.2 paints pictures without a transform, so a picture turns by its pixels: `media::turn` turns decoded frames by quarter turns off the main thread, an asset keyed by the picture and the turn. An svg turns to any angle through its `Transformation`, so a control that turns shows one (`interaction::Rotatable`).
+1
View File
@@ -476,3 +476,4 @@ Per-component lines live in `tasks/`. Tags there:
| T44a | 1 | FAIL | twelve roots in nine components still said w_full: a root held in a variable, a match arm or an animation passed the sweep |
| T44a | 2 | FAIL | roots built in render(mut self), returned by shared helpers or passed through them still said w_full: fields, charts, state views, Steps, previews; five measured at content width in a padded card |
| T44a | 3 | PASS | the brief counted 43 components in the new tests; they hold 41 |
| T44b | 1 | FAIL | after a key, leaving the calendar grid kept a stale preview, since the guard swallowed the exit gpui records for the key; the shaped() doc grew to 40 words |
+1 -1
View File
@@ -15,7 +15,7 @@ use crate::{
typography::{Ellipsis, format::file_size},
};
/// A box in a picture's own shape: as wide as the picture, but no wider than `widest` or its container. Hand it to a flex column inside a block: taffy 0.13 sizes the column by its width before its cap.
/// A box in a picture's own shape: as wide as the picture, but no wider than `widest` or its container.
pub(crate) fn shaped(width: f32, height: f32, widest: Pixels) -> Div {
assert!(
width > 0.0 && height > 0.0,
+4 -3
View File
@@ -1,9 +1,9 @@
use std::rc::Rc;
use gpui::{
Animation, AnimationExt, App, ElementId, FocusHandle, FontWeight, InteractiveElement,
IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement, Styled, Window, div,
prelude::*, transparent_black,
Animation, AnimationExt, App, ElementId, FocusHandle, FontWeight, HoverListenerMode,
InteractiveElement, IntoElement, ParentElement, RenderOnce, StatefulInteractiveElement, Styled,
Window, div, prelude::*, transparent_black,
};
use jiff::{ToSpan, civil::Date};
@@ -328,6 +328,7 @@ impl RenderOnce for Calendar {
.grid()
.grid_cols(7)
.gap_0p5()
.hover_listener_mode(HoverListenerMode::InputModalityIndependent)
.on_hover(move |over, window, cx| {
if !*over && !window.last_input_was_keyboard() {
leave.update(cx, |hover, cx| {
+21
View File
@@ -236,3 +236,24 @@ fn keys_keep_the_preview_under_a_resting_pointer(cx: &mut TestAppContext) {
"the keyboard's day stays the preview"
);
}
#[gpui::test]
fn leaving_the_grid_after_a_key_clears_the_preview(cx: &mut TestAppContext) {
setup(cx);
for key in ["right", "a", "pagedown"] {
let (view, cx) = cx.add_window_view(|_, _| Watched { seen: Vec::new() });
cx.update(|window, cx| window.focus_next(cx));
cx.simulate_mouse_move(point(px(120.0), px(200.0)), None, Modifiers::none());
cx.simulate_keystrokes(key);
cx.update(|window, _| window.refresh());
cx.run_until_parked();
cx.simulate_mouse_move(point(px(700.0), px(480.0)), None, Modifiers::none());
cx.update(|window, _| window.refresh());
cx.run_until_parked();
assert_eq!(
view.read_with(cx, |view, _| view.seen.last().copied().flatten()),
None,
"after {key}, leaving the grid clears its preview"
);
}
}