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

Add is_target to Click event #913

Merged
merged 2 commits into from
Sep 1, 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
25 changes: 17 additions & 8 deletions demo/dialog.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Simple dialog that looks similar to Angular Component Dialog."""

from typing import Callable

import mesop as me


Expand All @@ -17,7 +19,10 @@ class State:
def app():
state = me.state(State)

with dialog(state.is_open):
with dialog(
is_open=state.is_open,
on_click_background=on_click_close_background,
):
me.text("Delete File", type="headline-5")
with me.box():
me.text(text="Would you like to delete cat.jpeg?")
Expand All @@ -31,6 +36,12 @@ def app():
)


def on_click_close_background(e: me.ClickEvent):
state = me.state(State)
if e.is_target:
state.is_open = False


def on_click_close_dialog(e: me.ClickEvent):
state = me.state(State)
state.is_open = False
Expand All @@ -42,18 +53,15 @@ def on_click_dialog_open(e: me.ClickEvent):


@me.content_component
def dialog(is_open: bool):
def dialog(*, is_open: bool, on_click_background: Callable | None = None):
"""Renders a dialog component.

The design of the dialog borrows from the Angular component dialog. So basically
rounded corners and some box shadow.

One current drawback is that it's not possible to close the dialog
by clicking on the overlay background. This is due to
https://github.com/google/mesop/issues/268.

Args:
is_open: Whether the dialog is visible or not.
on_click_background: Event handler for when background is clicked
"""
with me.box(
style=me.Style(
Expand All @@ -67,14 +75,15 @@ def dialog(is_open: bool):
position="fixed",
width="100%",
z_index=1000,
)
),
):
with me.box(
on_click=on_click_background,
style=me.Style(
place_items="center",
display="grid",
height="100vh",
)
),
):
with me.box(
style=me.Style(
Expand Down
2 changes: 2 additions & 0 deletions mesop/component_helpers/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,9 +470,11 @@ def register_event_mapper(
ClickEvent,
lambda userEvent, key: ClickEvent(
key=key.key,
is_target=userEvent.click.is_target,
),
)


runtime().register_event_mapper(
InputEvent,
lambda userEvent, key: InputEvent(
Expand Down
4 changes: 4 additions & 0 deletions mesop/components/button/button.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {MatButtonModule} from '@angular/material/button';
import {Component, Input} from '@angular/core';
import {
ClickEvent,
UserEvent,
Key,
Type,
Expand Down Expand Up @@ -37,6 +38,9 @@ export class ButtonComponent {
const userEvent = new UserEvent();
userEvent.setHandlerId(this.config().getOnClickHandlerId()!);
userEvent.setKey(this.key);
const click = new ClickEvent();
click.setIsTarget(event.target === event.currentTarget);
userEvent.setClick(click);
this.channel.dispatch(userEvent);
}

Expand Down
3 changes: 2 additions & 1 deletion mesop/events/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ class ClickEvent(MesopEvent):

Attributes:
key (str): key of the component that emitted this event.
is_target (bool): Whether the clicked target is the component which attached the event handler.
"""

pass
is_target: bool


@dataclass(kw_only=True)
Expand Down
3 changes: 3 additions & 0 deletions mesop/examples/testing/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from mesop.examples.testing import (
click_is_target as click_is_target,
)
from mesop.examples.testing import (
complex_layout as complex_layout,
)
Expand Down
25 changes: 25 additions & 0 deletions mesop/examples/testing/click_is_target.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import mesop as me


@me.stateclass
class State:
box_clicked: bool
button_clicked: bool


@me.page(path="/testing/click_is_target")
def page():
state = me.state(State)
with me.box(on_click=on_click_box, style=me.Style(background="red")):
me.button("Click", on_click=on_click_button, type="flat")
me.text(f"Box clicked: {state.box_clicked}")
me.text(f"Button clicked: {state.button_clicked}")


def on_click_box(e: me.ClickEvent):
if e.is_target:
me.state(State).box_clicked = True


def on_click_button(e: me.ClickEvent):
me.state(State).button_clicked = True
8 changes: 7 additions & 1 deletion mesop/protos/ui.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ message QueryParam {
repeated string values = 2;
}

// Next ID: 16
// Next ID: 17
message UserEvent {
optional States states = 1;

Expand All @@ -46,6 +46,7 @@ message UserEvent {
ResizeEvent resize = 10;
bytes bytes_value = 9;
ChangePrefersColorScheme change_prefers_color_scheme = 14;
ClickEvent click = 16;
}

optional string state_token = 12;
Expand Down Expand Up @@ -78,6 +79,11 @@ message ArgPathSegment {
}
}

// Click event parameters
message ClickEvent {
optional bool is_target = 1;
}

// This is a user-triggered navigation (e.g. go back/forwards) or a hot reload event.
message NavigationEvent{
}
Expand Down
19 changes: 19 additions & 0 deletions mesop/tests/e2e/click_is_target_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {test, expect} from '@playwright/test';

test('is_target', async ({page}) => {
await page.goto('/testing/click_is_target');
await expect(page.getByText('Box clicked: False')).toBeVisible();
await expect(page.getByText('Button clicked: False')).toBeVisible();

await page.getByRole('button', {name: 'Click'}).click();
await expect(page.getByText('Box clicked: False')).toBeVisible();
await expect(page.getByText('Button clicked: True')).toBeVisible();

(
await page.locator(
'//component-renderer[contains(@style, "background: red")]',
)
).click();
await expect(page.getByText('Box clicked: True')).toBeVisible();
await expect(page.getByText('Button clicked: True')).toBeVisible();
});
4 changes: 4 additions & 0 deletions mesop/web/src/component_renderer/component_renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@angular/core';
import {CommonModule} from '@angular/common';
import {
ClickEvent,
Component as ComponentProto,
UserEvent,
WebComponentType,
Expand Down Expand Up @@ -414,6 +415,9 @@ Make sure the web component name is spelled the same between Python and JavaScri
const userEvent = new UserEvent();
userEvent.setHandlerId(this._boxType.getOnClickHandlerId()!);
userEvent.setKey(this.component.getKey());
const click = new ClickEvent();
click.setIsTarget(event.target === event.currentTarget);
userEvent.setClick(click);
this.channel.dispatch(userEvent);
}

Expand Down
Loading