Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
<!-- markdownlint-configure-file { "no-duplicate-heading": { "siblings_only": true } } -->

<!-- markdownlint-disable-next-line first-line-h1 -->
## Unreleased

### Added

* `MultiSelect` takes `params` (`MultiSelectParams`): `MultiSelectMode::Exclusive`, where a click
selects only the clicked option and Ctrl/⌘+click adds it or takes it away, as in
a `<select multiple>`; `none_label` for a button which clears the selection, f. ex. "All"
of a filter; and the CSS of the container and of the buttons, selected or not
* `MultiSelect`: every button tells whether it is selected in `aria-pressed`

### Changed

* `MultiSelect` needs `params` - `params={}` keeps how it worked and looked
* Needs vertigo with the modifier keys in `ClickEvent`

## 0.2.3 - 2026-10-21

### Changed
Expand Down
67 changes: 52 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions e2e/tests/storybook/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ fn main() {
switch::switches_share_the_value,
select::choosing_option_sets_value,
multi_select::buttons_toggle_values,
multi_select::filter_click_selects_one_ctrl_click_adds,
multi_drop_down::drop_down_toggles_values,
select_search::typing_filters_and_click_selects,
select_search::arrows_and_enter_select,
Expand Down
63 changes: 62 additions & 1 deletion e2e/tests/storybook/multi_select.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Story "MultiSelect": `MultiSelect` of strings, a button for each option.
//! Story "MultiSelect": `MultiSelect` of strings, a button for each option, and one as a filter
//! (`MultiSelectMode::Exclusive` with "All").

use vertigo_forms_e2e::prelude::*;

pub const SELECTED: &str = "//p[starts-with(normalize-space(.), 'Selected values:')]";
const FILTERED: &str = "//p[starts-with(normalize-space(.), 'Filtered by:')]";

/// Waits until exactly the `selected` options are marked as chosen (in bold).
pub async fn wait_for_marked(ctx: &Ctx, selected: &[&str]) -> Result<()> {
Expand Down Expand Up @@ -38,3 +40,62 @@ pub async fn buttons_toggle_values(ctx: Ctx) -> Result<()> {
.await?;
wait_for_marked(&ctx, &["bar", "baz"]).await
}

/// Waits until exactly the `pressed` buttons of the filter say so in `aria-pressed`.
async fn wait_for_pressed(ctx: &Ctx, pressed: &[&str]) -> Result<()> {
let filter_buttons = |state: &str| {
By::XPath(format!(
"//div[starts-with(normalize-space(.), 'Filter')]//button[@aria-pressed='{state}']"
))
};
ctx.wait_for(
&format!("filter buttons {pressed:?} to be pressed"),
async || {
let on = ctx.texts(filter_buttons("true")).await?;
let off = ctx.texts(filter_buttons("false")).await?;
Ok((on == pressed && on.len() + off.len() == 4).then_some(()))
},
)
.await
}

/// Clicks the button `label` with Ctrl held.
async fn ctrl_click(ctx: &Ctx, label: &str) -> Result<()> {
let button = ctx.button(label).await?;
ctx.action_chain()
.key_down(Key::Control)
.click_element(&button)
.key_up(Key::Control)
.perform()
.await?;
Ok(())
}

pub async fn filter_click_selects_one_ctrl_click_adds(ctx: Ctx) -> Result<()> {
let filtered = By::XPath(FILTERED);

ctx.open("/multi_select").await?;
ctx.wait_for_text_of(filtered.clone(), "Filtered by: red")
.await?;
wait_for_pressed(&ctx, &["red"]).await?;

ctx.click_button("green").await?;
ctx.wait_for_text_of(filtered.clone(), "Filtered by: green")
.await?;
ctrl_click(&ctx, "blue").await?;
ctx.wait_for_text_of(filtered.clone(), "Filtered by: green,blue")
.await?;
wait_for_pressed(&ctx, &["green", "blue"]).await?;
ctrl_click(&ctx, "green").await?;
ctx.wait_for_text_of(filtered.clone(), "Filtered by: blue")
.await?;

// A plain click leaves only the clicked one, "All" none
ctrl_click(&ctx, "red").await?;
ctx.click_button("red").await?;
ctx.wait_for_text_of(filtered.clone(), "Filtered by: red")
.await?;
ctx.click_button("All").await?;
ctx.wait_for_text_of(filtered, "Filtered by: all").await?;
wait_for_pressed(&ctx, &["All"]).await
}
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ pub use {
input::{Input, InputWithButton, InputWithButtonParams, ListInput},
popup::{Popup, PopupOnHover, PopupParams},
search_panel::{SearchPanel, SearchPanelParams, SearchResult},
select::{DictSelect, MultiDropDown, MultiDropDownParams, MultiSelect, Select},
select::{
DictSelect, MultiDropDown, MultiDropDownParams, MultiSelect, MultiSelectMode,
MultiSelectParams, Select,
},
select_search::{SelectSearch, SelectSearchParams},
spinner::Spinner,
switch::{Switch, SwitchParams},
Expand Down
1 change: 1 addition & 0 deletions src/select/multi_drop_down.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ where
<MultiSelect
value={&self.value}
options={&self.options}
params={}
/>
</div>
}
Expand Down
Loading
Loading