Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple selections on select box #481

Merged
merged 2 commits into from
Jun 20, 2024
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
7 changes: 4 additions & 3 deletions demo/select_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

@me.stateclass
class State:
selected_value: str = ""
selected_values: list[str]


def on_selection_change(e: me.SelectSelectionChangeEvent):
s = me.state(State)
s.selected_value = e.value
s.selected_values = e.values


@me.page(
Expand All @@ -28,6 +28,7 @@ def app():
],
on_selection_change=on_selection_change,
style=me.Style(width=500),
multiple=True,
)
s = me.state(State)
me.text(text="Selected value: " + s.selected_value)
me.text(text="Selected values: " + ", ".join(s.selected_values))
1 change: 1 addition & 0 deletions mesop/components/select/e2e/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from . import select_app as select_app
from . import select_app_multiple as select_app_multiple
29 changes: 29 additions & 0 deletions mesop/components/select/e2e/select_app_multiple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import mesop as me


@me.stateclass
class State:
selected_values: list[str]


def on_selection_change(e: me.SelectSelectionChangeEvent):
s = me.state(State)
s.selected_values = e.values


@me.page(path="/components/select/e2e/select_app_multiple")
def app():
me.text(text="Select")
me.select(
label="Select",
options=[
me.SelectOption(label="label 1", value="value1"),
me.SelectOption(label="label 2", value="value2"),
me.SelectOption(label="label 3", value="value3"),
],
on_selection_change=on_selection_change,
multiple=True,
style=me.Style(width=500),
)
s = me.state(State)
me.text(text="Selected values: " + ", ".join(s.selected_values))
22 changes: 18 additions & 4 deletions mesop/components/select/e2e/select_test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import {test, expect} from '@playwright/test';

test('test', async ({page}) => {
test('single selection', async ({page}) => {
await page.goto('/components/select/e2e/select_app');
await page.getByRole('combobox').click();
await page.getByRole('option', {name: 'label 2'}).click();
await expect(page.getByText('Selected value: value2')).toBeAttached();

await page.getByRole('combobox').click();
await page.getByRole('option', {name: 'label 3'}).click();
await expect(page.getByText('Selected value: value3')).toBeAttached();
});

test('multiple selection', async ({page}) => {
await page.goto('/components/select/e2e/select_app_multiple');
await page.getByLabel('Select').click();

await page.getByRole('option', {name: 'label 2'}).click();
await expect(page.getByText('Selected values: value2')).toBeAttached();

expect(
await page.getByText('Selected value: value2').textContent(),
).toContain('Selected value: value2');
await page.getByRole('option', {name: 'label 1'}).click();
await expect(
page.getByText('Selected values: value1, value2'),
).toBeAttached();
});
1 change: 1 addition & 0 deletions mesop/components/select/select.ng.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
[value]="config().getValue()"
(openedChange)="onSelectOpenedChangeEvent($event)"
(selectionChange)="onSelectSelectionChangeEvent($event)"
[multiple]="config().getMultiple()"
>
@for(option of config().getOptionsList(); track $index) {
<mat-option [value]="option.getValue()">{{option.getLabel()}}</mat-option>
Expand Down
5 changes: 5 additions & 0 deletions mesop/components/select/select.proto
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ syntax = "proto2";

package mesop.components.select;

message SelectChangeEvent {
repeated string values = 1;
}

message SelectType {
optional bool disabled = 2;
optional bool disable_ripple = 3;
optional bool multiple = 18;
optional double tab_index = 4;
optional string placeholder = 6;
optional string value = 9;
Expand Down
31 changes: 22 additions & 9 deletions mesop/components/select/select.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,33 @@ class SelectOpenedChangeEvent(MesopEvent):

@dataclass(kw_only=True)
class SelectSelectionChangeEvent(MesopEvent):
"""Event representing a change in the select component's value.
"""Event representing a change in the select component's value(s).
Attributes:
richard-to marked this conversation as resolved.
Show resolved Hide resolved
value: The new value of the select component after the change.
values: New values of the select component after the change.
key (str): Key of the component that emitted this event.
"""

value: str
values: list[str]

@property
def value(self):
"""Shortcut for returning a single value."""
if not self.values:
return ""
return self.values[0]

register_event_mapper(
SelectSelectionChangeEvent,
lambda event, key: SelectSelectionChangeEvent(

def map_select_change_event(event, key):
select_event = select_pb.SelectChangeEvent()
select_event.ParseFromString(event.bytes_value)
return SelectSelectionChangeEvent(
key=key.key,
value=event.string_value,
),
)
values=list(select_event.values),
)


register_event_mapper(SelectSelectionChangeEvent, map_select_change_event)


@dataclass(kw_only=True)
Expand Down Expand Up @@ -82,6 +92,7 @@ def select(
placeholder: str = "",
value: str = "",
style: Style | None = None,
multiple: bool = False,
):
"""Creates a Select component.
Expand All @@ -91,6 +102,7 @@ def select(
on_opened_change: Event emitted when the select panel has been toggled.
disabled: Whether the select is disabled.
disable_ripple: Whether ripples in the select are disabled.
multiple: Whether multiple selections are allowed.
tab_index: Tab index of the select.
placeholder: Placeholder to be shown if no value has been selected.
value: Value of the select control.
Expand All @@ -109,6 +121,7 @@ def select(
label=label,
disabled=disabled,
disable_ripple=disable_ripple,
multiple=multiple,
tab_index=tab_index,
placeholder=placeholder,
value=value,
Expand Down
16 changes: 13 additions & 3 deletions mesop/components/select/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import {
Type,
Style,
} from 'mesop/mesop/protos/ui_jspb_proto_pb/mesop/protos/ui_pb';
import {SelectType} from 'mesop/mesop/components/select/select_jspb_proto_pb/mesop/components/select/select_pb';
import {
SelectChangeEvent,
SelectType,
} from 'mesop/mesop/components/select/select_jspb_proto_pb/mesop/components/select/select_pb';
import {Channel} from '../../web/src/services/channel';
import {formatStyle} from '../../web/src/utils/styles';

Expand Down Expand Up @@ -45,11 +48,18 @@ export class SelectComponent {

onSelectSelectionChangeEvent(event: MatSelectChange): void {
const userEvent = new UserEvent();

userEvent.setHandlerId(
this.config().getOnSelectSelectionChangeEventHandlerId()!,
);
userEvent.setStringValue(event.value);
const changeEvent = new SelectChangeEvent();
if (typeof event.value === 'string') {
changeEvent.addValues(event.value);
} else {
for (const value of event.value) {
changeEvent.addValues(value);
}
}
userEvent.setBytesValue(changeEvent.serializeBinary());
userEvent.setKey(this.key);
this.channel.dispatch(userEvent);
}
Expand Down
Loading