Skip to content

Select Options disalbed rework#176

Open
knoxfighter wants to merge 4 commits intoDioxusLabs:mainfrom
knoxfighter:select-focus-disabled
Open

Select Options disalbed rework#176
knoxfighter wants to merge 4 commits intoDioxusLabs:mainfrom
knoxfighter:select-focus-disabled

Conversation

@knoxfighter
Copy link
Contributor

This contains a lot of changes and fixes for the Select component to make disabling options properly work (see #169).

  • I had to write custom focus_prev, focus_last, focus_next, focus_first, etc. functions that are independent of the ones in FocusState. This also means that i removed FocusState from the SelectContext component.
  • The options Vec is now a BTreeMap, to make it easy to sort via tabindex.
  • Small aria adjustments
  • Additional tests

I hope this code is up to your standards and the idea is something you are willing to use.

@github-actions
Copy link

@knoxfighter
Copy link
Contributor Author

I forgot to run clippy..
There is just one question: What should i do with FocusState::item_count()? It is not used anymore, but it might be interesting for other components.

@ealmloff
Copy link
Member

What part of the select focus management needs to be different in select vs other components? This PR fixes the issues for select but if we can integrate those fixes into the hook it would be better so it works for the other components

@knoxfighter
Copy link
Contributor Author

knoxfighter commented Dec 29, 2025

Since I decided to move the focus functions directly into the SelectContext, it now uses more data than is theoretically necessary.

The options Vec (now a Map) is used with iterators to find the next element. This could be changed back to a Vec when using the original FocusState, by using its index for access, provided the vector is kept sorted.

Each option in the options map has a disabled field indicating whether it is disabled. This information is required but is not available when using FocusState. This is especially important because I plan to extend this field to also reflect whether an option is filtered based on user input (for a future ComboBox implementation).

I am not sure how this information could be made available in FocusState. One idea would be to add an optional Vec<Signal<bool>> (though that reuires the sorted array index to be synced and each option to know its index) to FocusState. What do you think?

@ealmloff
Copy link
Member

There is a variant of the existing focus hook here that only adds the item to the focus state's list if it isn't disabled. I imagine we could do something similar for select?

@knoxfighter
Copy link
Contributor Author

The FocusState does only hold the amount of possible elements. When that value is lowered by a disabled option, then the last option is not focusable, not the one that is disabled (use_focus_entry_disabled is the function for that),
It might be possible to step over disabled elements in use_focus_control_disabled that runs next/prev again, but that needs an additional field to know the current direction.
And this doesn't ensure that the tab_index is correctly used (aka. the tab index has to start with 0/1 and has to be consecutive).

@ealmloff
Copy link
Member

Feel free to make changes to FocusState, if it has the wrong behavior. The behavior in this PR seems right if we can get it merged into the main hook

@knoxfighter
Copy link
Contributor Author

I added that feature now directly to the FocusState. I've added a IndexMap with the tab_index and the disabled state (i hope that dependency is fine?). Those values can now also change dynamically and the list will update accordingly. The tab_index is still mandatory, but it now can be out of order and even multiple elements with the same value can exist.

These Components could also benefit from the changes made to the FocusState. They are adjusted to conform changes in this PR without any functionality changes. If this PR is merged, I will create further PRs with those changes.

  • ContextMenu
  • DatePicker (only fixes)
  • DropdownMenu
  • Menubar
  • Navbar
  • Tabs
  • ToggleGroup

Exception to that is the RadioGroup, that had similar issues as the Select component (disabled elements in the mid of the group would cause the last element to be unreachable), which is now also fixed.

@knoxfighter knoxfighter force-pushed the select-focus-disabled branch from 2ac10ff to d284d89 Compare January 9, 2026 16:44
@knoxfighter
Copy link
Contributor Author

Since this is now open without any response for 2 months:
Is there an issue with this PR? Should i adjust anything? Or is this not how you want to have it implemented?

/// The index of the option in the list. This is used to define the focus order for keyboard navigation.
pub index: ReadSignal<usize>,
/// The tab_index of the option in the list. This is used to define the focus order for keyboard navigation.
pub tab_index: ReadSignal<usize>,
Copy link
Member

Choose a reason for hiding this comment

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

Tab index is a different property which is not what we are setting here. It needs to be the index within the select element. Tab index generally should never have a value other than -1 or 0 which is not the case here

/// The focus state for the select
pub focus_state: FocusState,
/// A list of options with their states
pub(crate) options: Signal<HashMap<usize, OptionState>>,
Copy link
Member

Choose a reason for hiding this comment

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

The options should have sequential ids some of which may be disabled. Why do we need a hashmap here?

}

/// If you don't already have a unique id, use this hook to generate one.
pub(crate) fn use_focus_unique_id() -> usize {
Copy link
Member

@ealmloff ealmloff Mar 13, 2026

Choose a reason for hiding this comment

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

unsure why we would need a unique id for focus. the order we focus items in should be deterministic and sequential. this can return re-used ids in fullstack if the server crates the id 0, the client will not recognize that id has already been used when assigning new ids during streaming

ctx: FocusState,
index: impl Readable<Target = usize> + Copy + 'static,
id: usize,
tab_index: impl Readable<Target = usize> + Copy + 'static,
Copy link
Member

Choose a reason for hiding this comment

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

both of these should be unique and it looks like you always pass the same value for id and tab_index. Do we need id?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants