Follow-up to #2187.
When the popover closes, it previously always restored focus to the
previously focused element. This could cause a race condition: if
another element had already taken focus (e.g., a second popover
opening), the restore would incorrectly steal focus back.
Fix: only restore the previous focus handle if the popover's focus
handle still contains focus at the time of closing.
```diff
if let Some(prev) = self.previous_focus_handle.take() {
+ if self.focus_handle.contains_focused(window, cx) {
prev.focus(window, cx);
+ }
}
```
When a popover opens, the previously focused element is now saved. When
the popover closes, focus is automatically restored to that element, so
keyboard navigation is not disrupted after dismissing a popover.
## Description
Previously hitting `end` or `home` would move the cursor to the
end/start of the line even if it was wrapped over several lines, now it
moves it to the end of the visual/wrapped line instead. This matches
VSCode
## Videos
### Before
https://github.com/user-attachments/assets/b104c8b4-1d81-45ad-a0d8-6d21d1f4a12a
### After
https://github.com/user-attachments/assets/46beee60-4eac-41a9-b2d7-17042d33149e
## How to Test
Open the editor example, enable soft wrap and try using home and end
## Checklist
- [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [x] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [x] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)
## Summary
Add `color(impl Into<Hsla>)` builder method to `Switch`, allowing
callers to customize the background color when the switch is checked.
Defaults to `theme.primary` to preserve existing behavior.
```rust
Switch::new("id")
.checked(true)
.color(cx.theme().success) // green
Switch::new("id")
.checked(true)
.color(cx.theme().danger) // red
```
## Preview
<img width="676" height="104" alt="image"
src="https://github.com/user-attachments/assets/b1422295-e463-4075-b2f2-475c71ef7ac9"
/>
## Changes
- `crates/ui/src/switch.rs`: Add `color` field and builder method
- `crates/story/src/stories/switch_story.rs`: Add "Custom Color" section
- `docs/docs/components/switch.md`: Document `color` method
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Add `menu_max_h(impl Into<Length>)` builder method to `Select`, allowing
callers to customize the dropdown menu's max height. Defaults to
`rems(20.)` to preserve existing behavior.
```rust
Select::new(&state).menu_max_h(rems(10.))
Select::new(&state).menu_max_h(px(300.))
```
## Changes
- `crates/ui/src/select.rs`: Add `menu_max_h` field to `SelectOptions`
and builder method
- `crates/story/src/stories/select_story.rs`: Add "Custom Menu Max
Height" section
- `docs/docs/components/select.md`: Document `menu_max_h` under Custom
Appearance
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary
Fixes multi-paragraph list items rendering with a horizontal 50/50 split
instead of stacking vertically.
**Note:** This PR contains only one fix. The `flex_1()` fix for list
item text clipping (adding `.flex_1()` before `.overflow_hidden()` on
the text wrapper) is already present on `main` — so only the
continuation paragraph fix is included here.
## The Bug
When a Markdown list item contains multiple paragraphs (continuation
paragraphs), the existing code merges the continuation into the previous
`h_flex` row using `item_item.extend(...)`. Because `h_flex` distributes
space horizontally, the first paragraph and continuation paragraph each
get roughly 50% width — causing text to split side by side instead of
stacking top to bottom.
**Example Markdown:**
```markdown
- First paragraph of the list item.
Continuation paragraph that should appear below.
```
**Before (broken):** Both paragraphs render side by side in a horizontal
row.
**After (fixed):** Continuation paragraph stacks below the first,
indented past the bullet prefix.
**After***:
<img width="779" height="331" alt="image"
src="https://github.com/user-attachments/assets/f47253bf-316a-4e0c-9883-f8c976c79bb3"
/>
## The Fix
Replace the horizontal `extend()` with a `v_flex()` that:
1. Pops the previous paragraph row from items
Pops the previous paragraph row from items
3. Wraps both in a `v_flex` (vertical stack)
4. Indents the continuation with `pl(rems(1.0))` to align past the
bullet/number prefix
```rust
// Before: horizontal merge (broken)
if let Some(item_item) = items.last_mut() {
item_item.extend(vec![
div().overflow_hidden().child(text).into_any_element(),
]);
// After: vertical stack (fixed)
if let Some(first_paragraph_row) = items.pop() {
items.push(
v_flex()
.child(first_paragraph_row)
.child(div().pl(rems(1.0)).overflow_hidden().child(text)),
);
```
## Context
Discovered while using gpui-component v0.5.1 for Markdown rendering in a
desktop app. The fix is minimal and only affects the `render_list_item`
function in `crates/ui/src/text/node.rs`. `cargo check` passes cleanly.
---------
Co-authored-by: klausi <git.ku@gc4.at>
Co-authored-by: Jason Lee <huacnlee@gmail.com>
## Breaking Change
`ContextMenu` must be bound to elements that implement
`InteractiveElement` to avoid duplicate ID issues.
```diff
- div().context_menu()
+ div().id("foo").context_menu()
```
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes #[issue number]
## Description
Expose a small public accessor on `InputState` to retrieve the rendered
bounds for a UTF-8 byte range.
## Screenshot
| Before | After |
| ---------------------------- | --------------------------- |
| [Put Before Screenshot here] | [Put After Screenshot here] |
## How to Test
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce.
## Checklist
- [X] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [X] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [X] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)
`occlude` will cause elements beneath the scrollbar to be unable to
respond to events.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes#2157
## Description
Fix markdown list item clipping in narrow containers by making the list
item wrapper, row, and text body all shrink correctly under GPUI/Taffy
flex layout. This prevents list item text from being laid out too wide
beside the bullet/prefix area and getting clipped on the right edge
instead of wrapping.
This PR also adds a small local reproduction example for issue #2157 so
the behavior can be checked directly.
## Screenshot
| Before | After |
| --- | --- |
|

|
|
## How to Test
1. Run `cargo run -p issue_2157_markdown_list_clipping`.
2. Verify long markdown list items wrap fully inside the 520px
container.
3. Confirm the trailing glyphs are no longer clipped on the right edge.
## Checklist
- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [x] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [ ] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)
Closes#2155
## Description
Fix clipped-region hit testing for markdown content rendered by
`TextView`. `TextView` now uses a hitbox-aware check when starting or
clearing selection, and `Inline` link clicks use the same
clipped-region-aware hit testing instead of raw bounds checks. This
prevents hidden markdown from remaining interactive when it is clipped
by `overflow_hidden()` or covered by sibling content.
## Screenshot
### Before
https://github.com/user-attachments/assets/77d5a9f1-53c3-47c3-a958-bc1806630dcd
### After
https://github.com/user-attachments/assets/67144c0f-08ad-4c90-9636-9a68ab8934bd
## How to Test
Run:
```bash
cargo test -p gpui-component clipped_markdown_ -- --nocapture
```
Then verify that clicking or dragging over a visually clipped markdown
region no longer starts selection or opens a link.
## Checklist
- [ ] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [x] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [ ] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)
## Description
The old method was O(n^2) with the styles loop inside of the intervals
loop. This changes it to O(n log n) + an additional loop over the
styles. ~60x improvement on really long lines which is where it had
trouble.
```
OLD: styles on 63000 byte nested HTML took 2.948660759s, highlights 32286
NEW: styles on 63000 byte nested HTML took 49.565413ms, highlights 32286
```
## How to Test
I tested it with this in `highlighter.rs`. But I didn't include it in
the PR because it's weird to assert on benchmarks.
```rust
#[test]
#[cfg(feature = "tree-sitter-languages")]
fn test_long_lines() {
// Generate alot of nested HTML tags
let tags = ["div", "p", "span", "strong", "i", "u"];
let mut html = String::new();
for _ in 0..1000 {
for tag in &tags {
html.push_str(&format!("<{tag}>"));
}
html.push_str("x");
for tag in tags.iter().rev() {
html.push_str(&format!("</{tag}>"));
}
}
let rope = Rope::from_str(&html);
let mut highlighter = SyntaxHighlighter::new("html");
highlighter.update(None, &rope);
let highlight_theme = HighlightTheme::default_light();
let start = std::time::Instant::now();
let range = 6500..63000;
let highlights = highlighter.styles(&range, &highlight_theme);
let elapsed = start.elapsed();
eprintln!(
"styles on {} byte nested HTML took {:?}, highlights {}",
html.len(),
elapsed,
highlights.len(),
);
assert!(!highlights.is_empty(), "Should produce highlights");
}
```
## Checklist
- [x] I have read the [CONTRIBUTING](../CONTRIBUTING.md) document and
followed the guidelines.
- [x] Reviewed the changes in this PR and confirmed AI generated code
(If any) is accurate.
- [x] Passed `cargo run` for story tests related to the changes.
- [ ] Tested macOS, Windows and Linux platforms performance (if the
change is platform-specific)