Skip to content

Conversation

@huacnlee
Copy link
Contributor

@huacnlee huacnlee commented Jul 21, 2025

Continue #33008 to add a focus_trap option method for let tabs with in group.

This is used to help us to manage the focus loop when in some case like Modal, Popover.

Screen.Recording.2025-07-28.at.11.56.07.mov

Release Notes:

  • N/A

@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Jul 21, 2025
@huacnlee huacnlee changed the title gpui: Add to support group the tab cycle gpui: Add tab_group to group the tab focus Jul 21, 2025
@huacnlee
Copy link
Contributor Author

Ready for review

@huacnlee

This comment was marked as resolved.

@huacnlee
Copy link
Contributor Author

Maybe div().tab_group() can be use div().occlude() to avoid add a new API, but if true we need to rename HitboxBehavior::BlockMouse to HitboxBehavior::Block (Or add a new one).

fn occlude(mut self) -> Self {
self.interactivity().occlude_mouse();
self
}

/// All hitboxes behind this hitbox will be ignored and so will have `hitbox.is_hovered() ==
/// false` and `hitbox.should_handle_scroll() == false`. Typically for elements this causes
/// skipping of all mouse events, hover styles, and tooltips. This flag is set by
/// [`InteractiveElement::occlude`].
///
/// For mouse handlers that check those hitboxes, this behaves the same as registering a
/// bubble-phase handler for every mouse event type:
///
/// ```
/// window.on_mouse_event(move |_: &EveryMouseEventTypeHere, phase, window, cx| {
/// if phase == DispatchPhase::Capture && hitbox.is_hovered(window) {
/// cx.stop_propagation();
/// }
/// }
/// ```
///
/// This has effects beyond event handling - any use of hitbox checking, such as hover
/// styles and tooltops. These other behaviors are the main point of this mechanism. An
/// alternative might be to not affect mouse event handling - but this would allow
/// inconsistent UI where clicks and moves interact with elements that are not considered to
/// be hovered.
BlockMouse,

After a moment think, I think split name is better, because occlude will happen in some case but that we not want to block tab focus.

@huacnlee
Copy link
Contributor Author

huacnlee commented Jul 21, 2025

Use for GPUI Component example:

longbridge/gpui-component#991

Screen.Recording.2025-07-22.at.19.08.25.mov

Copy link
Contributor

@tidely tidely left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good, just some tiny nits that keep the code clear

prev_ix = self.handles.len().saturating_sub(1);
prev_ix = group_handles.len().saturating_sub(1);
} else {
prev_ix = ix.saturating_sub(1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We check that ix is larger than 0 before, so this doesn't need to be saturating

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, even the ix is 0, the group_handles.len() may also 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should’ve been more clear with what line I meant, that’s my bad. I think it only applies to prev_ix = ix.saturating_sub(1); since we know ix isn’t zero

@huacnlee huacnlee force-pushed the focus-trap branch 2 times, most recently from ed0ec39 to b361b4a Compare July 22, 2025 02:13

/// Set the tab group of this focus handle.
///
/// If set, the focus cycle will loop in same group.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want focus to cycle when hitting the edge of a tab group. Rather, I think it would make more sense if focus continued on to the next tab stop after the group.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, should you please explain more?
I am not got your point.

Copy link
Member

@mikayla-maki mikayla-maki Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this UI:

<tab-index = 0> <!---- If you hit tab here -->
<tab-group  tab-index = 1>
  <tab-index = 3>
  <tab-index = 2> <!----- This is where you should end up -->
</tab-group>
<tab-index = 2>

Calling focus_next repeatedly should result in an order like 0->2->3->1->0..., not 0->2->3->2->3->2->3...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see.

@huacnlee
Copy link
Contributor Author

huacnlee commented Jul 28, 2025

@mikayla-maki Let me explain the meaning of this feature:

The original intention is to add restrictions to tab focus for Modal and Popup scenarios, so that when Popup or Modal is opened, the tab focus always remains within the scope of the Modal, and is restored to other places after closing.

Screen.Recording.2025-07-28.at.11.56.07.mov

There is no such feature in HTML, but front-end components often implement it, and they use focus trap, such as:

Similar to Chrome:
https://github.com/user-attachments/assets/ab80ba44-89d5-42ce-a38f-2bdbbbf4d1aa

So, what if I rename this method to focus_trap?

@huacnlee huacnlee changed the title gpui: Add tab_group to group the tab focus gpui: Add focus_trap to group the tab focus Jul 28, 2025
@huacnlee huacnlee requested a review from mikayla-maki July 30, 2025 06:50
probably-neb pushed a commit that referenced this pull request Aug 5, 2025
This change adds the current tab index functionality to buttons and
implements a proof of concept for the new welcome page.

Primarily blocked on #34804,
secondarily on #35075 so we
can ensure navigation always works as intended.

Another thing to consider here is whether we want to assign the tab
order more implicitly / "automatically" based on the current layout
ordering. This would generally enable us to add a default order to
focusable elements if we want this. See [the
specification](https://html.spec.whatwg.org/multipage/interaction.html#flattened-tabindex-ordered-focus-navigation-scope)
on some more context on how the web usually handles this for focusable
elements.

Release Notes:

- N/A
orual pushed a commit to orual/zed that referenced this pull request Aug 23, 2025
This change adds the current tab index functionality to buttons and
implements a proof of concept for the new welcome page.

Primarily blocked on zed-industries#34804,
secondarily on zed-industries#35075 so we
can ensure navigation always works as intended.

Another thing to consider here is whether we want to assign the tab
order more implicitly / "automatically" based on the current layout
ordering. This would generally enable us to add a default order to
focusable elements if we want this. See [the
specification](https://html.spec.whatwg.org/multipage/interaction.html#flattened-tabindex-ordered-focus-navigation-scope)
on some more context on how the web usually handles this for focusable
elements.

Release Notes:

- N/A
@huacnlee huacnlee closed this Sep 25, 2025
@huacnlee
Copy link
Contributor Author

#38531 is merged.

huacnlee added a commit to longbridge/gpui-component that referenced this pull request Sep 29, 2025
Wait zed-industries/zed#33008,
zed-industries/zed#34804 to merge.


https://github.com/user-attachments/assets/3c21ebc9-44a0-4311-bb0f-5f63fe8d4bb3

- Improved `focus_ring` to render like an outline.
- Added to support press `Enter`, `Space` key to trigger Checkbox,
Button, Radio.

## Break Changes

- The `FocusableExt` has been changed to only for crate internal.
- The `FocusableCycle` has been removed, now GPUI has it own tab stop
implementation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants