Skip to content

Commit

Permalink
feat: Serialize press events for press event callbacks (#236)
Browse files Browse the repository at this point in the history
- Serialize press events so they get passed back to the server (Fixes
#76)
- Add pydocs for `action_button` (Fixes #235)

BREAKING CHANGE: All button `on_press` handlers now must take a
positional argument for the event. For example, when using an action
button:
```python
import deephaven.ui as ui
from deephaven.ui import use_state


@ui.component
def counter():
    count, set_count = use_state(0)
    return ui.action_button(
        # Any lambda passed into the `on_press` method now needs to accept the event argument
        f"You pressed me {count} times", on_press=lambda e: set_count(count + 1)
    )


c = counter()

```
  • Loading branch information
mofojed authored Feb 1, 2024
1 parent c312d43 commit 38b202f
Show file tree
Hide file tree
Showing 18 changed files with 720 additions and 105 deletions.
8 changes: 4 additions & 4 deletions docker/data/storage/notebooks/DEMO.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ We define our `counter` component as a function using the `@ui.component` decora
def counter():
count, set_count = ui.use_state(0)
return ui.action_button(
f"You pressed me {count} times", on_press=lambda: set_count(count + 1)
f"You pressed me {count} times", on_press=lambda _: set_count(count + 1)
)


Expand All @@ -48,7 +48,7 @@ def my_input():

return ui.flex(
ui.action_button(
f"You pressed me {count} times", on_press=lambda: set_count(count + 1)
f"You pressed me {count} times", on_press=lambda _: set_count(count + 1)
),
ui.text_field(value=text, on_change=set_text),
ui.text(f"You typed {text}"),
Expand Down Expand Up @@ -241,10 +241,10 @@ def order_table():
)
)

def handle_buy():
def handle_buy(_):
submit_order(sym, size, "buy")

def handle_sell():
def handle_sell(_):
submit_order(sym, size, "sell")

return [
Expand Down
2 changes: 2 additions & 0 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion plugins/ui/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ from deephaven.ui import use_state
def counter():
count, set_count = use_state(0)
return ui.action_button(
f"You pressed me {count} times", on_press=lambda: set_count(count + 1)
f"You pressed me {count} times", on_press=lambda _: set_count(count + 1)
)


Expand Down
34 changes: 29 additions & 5 deletions plugins/ui/src/deephaven/ui/_internal/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from __future__ import annotations

from typing import Any
from typing import Any, Callable

_UNSAFE_PREFIX = "UNSAFE_"


def get_component_name(component: Any) -> str:
Expand Down Expand Up @@ -46,23 +48,45 @@ def to_camel_case(snake_case_text: str) -> str:
The camelCase string.
"""
components = snake_case_text.split("_")
return components[0] + "".join(x.title() for x in components[1:])
return components[0] + "".join((x[0].upper() + x[1:]) for x in components[1:])


def to_camel_case_skip_unsafe(snake_case_text: str) -> str:
"""
Convert a snake_case string to camelCase. Leaves the `UNSAFE_` prefix intact if present.
Args:
snake_case_text: The snake_case string to convert.
Returns:
The camelCase string with the `UNSAFE_` prefix intact if present.
"""
if snake_case_text.startswith(_UNSAFE_PREFIX):
return _UNSAFE_PREFIX + to_camel_case(snake_case_text[len(_UNSAFE_PREFIX) :])
return to_camel_case(snake_case_text)


# TODO: Take an exclusion regex? function? for keys we do not want to convert
def dict_to_camel_case(snake_case_dict: dict[str, Any]) -> dict[str, Any]:
def dict_to_camel_case(
snake_case_dict: dict[str, Any],
omit_none: bool = True,
convert_key: Callable[[str], str] = to_camel_case_skip_unsafe,
) -> dict[str, Any]:
"""
Convert a dict with snake_case keys to a dict with camelCase keys.
Args:
snake_case_dict: The snake_case dict to convert.
omit_none: Whether to omit keys with a value of None.
convert_key: The function to convert the keys. Can be used to customize the conversion behaviour
Returns:
The camelCase dict.
"""
camel_case_dict: dict[str, Any] = {}
for key, value in snake_case_dict.items():
camel_case_dict[to_camel_case(key)] = value
if omit_none and value is None:
continue
camel_case_dict[convert_key(key)] = value
return camel_case_dict


Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .action_button import *
from .basic import *
from .flex import *
195 changes: 195 additions & 0 deletions plugins/ui/src/deephaven/ui/components/spectrum/action_button.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
from __future__ import annotations
from typing import Any, Callable
from .events import (
ButtonType,
FocusEventCallable,
KeyboardEventCallable,
PressEventCallable,
StaticColor,
)
from .layout import (
AlignSelf,
CSSProperties,
DimensionValue,
JustifySelf,
LayoutFlex,
Number,
Position,
)
from .basic import spectrum_element
from ...elements import Element


def action_button(
*children: Any,
type: ButtonType = "button",
on_press: PressEventCallable | None = None,
on_press_start: PressEventCallable | None = None,
on_press_end: PressEventCallable | None = None,
on_press_up: PressEventCallable | None = None,
on_press_change: Callable[[bool], None] | None = None,
on_focus: FocusEventCallable | None = None,
on_blur: FocusEventCallable | None = None,
on_focus_change: Callable[[bool], None] | None = None,
on_key_down: KeyboardEventCallable | None = None,
on_key_up: KeyboardEventCallable | None = None,
auto_focus: bool | None = None,
is_disabled: bool | None = None,
is_quiet: bool | None = None,
static_color: StaticColor | None = None,
flex: LayoutFlex | None = None,
flex_grow: Number | None = None,
flex_shrink: Number | None = None,
flex_basis: DimensionValue | None = None,
align_self: AlignSelf | None = None,
justify_self: JustifySelf | None = None,
order: Number | None = None,
grid_area: str | None = None,
grid_row: str | None = None,
grid_row_start: str | None = None,
grid_row_end: str | None = None,
grid_column: str | None = None,
grid_column_start: str | None = None,
grid_column_end: str | None = None,
margin: DimensionValue | None = None,
margin_top: DimensionValue | None = None,
margin_bottom: DimensionValue | None = None,
margin_start: DimensionValue | None = None,
margin_end: DimensionValue | None = None,
margin_x: DimensionValue | None = None,
margin_y: DimensionValue | None = None,
width: DimensionValue | None = None,
height: DimensionValue | None = None,
min_width: DimensionValue | None = None,
min_height: DimensionValue | None = None,
max_width: DimensionValue | None = None,
max_height: DimensionValue | None = None,
position: Position | None = None,
top: DimensionValue | None = None,
bottom: DimensionValue | None = None,
start: DimensionValue | None = None,
end: DimensionValue | None = None,
left: DimensionValue | None = None,
right: DimensionValue | None = None,
z_index: Number | None = None,
is_hidden: bool | None = None,
UNSAFE_class_name: str | None = None,
UNSAFE_style: CSSProperties | None = None,
) -> Element:
"""
ActionButtons allow users to perform an action. They're used for similar, task-based options within a workflow, and are ideal for interfaces where buttons aren't meant to draw a lot of attention.
Python implementation for the Adobe React Spectrum ActionButton component: https://react-spectrum.adobe.com/react-spectrum/ActionButton.html
Args:
*children: The content to display inside the button.
type: The type of button to render. (default: "button")
on_press: Function called when the button is pressed.
on_press_start: Function called when the button is pressed.
on_press_end: Function called when a press interaction ends, either over the target or when the pointer leaves the target.
on_press_up: Function called when the button is released.
on_press_change: Function called when the press state changes.
on_focus: Function called when the button receives focus.
on_blur: Function called when the button loses focus.
on_focus_change: Function called when the focus state changes.
on_key_down: Function called when a key is pressed.
on_key_up: Function called when a key is released.
auto_focus: Whether the button should automatically get focus when the page loads.
is_disabled: Whether the button is disabled.
is_quiet: Whether the button should be quiet.
static_color: The static color style to apply. Useful when the button appears over a color background.
flex: When used in a flex layout, specifies how the element will grow or shrink to fit the space available.
flex_grow: When used in a flex layout, specifies how much the element will grow to fit the space available.
flex_shrink: When used in a flex layout, specifies how much the element will shrink to fit the space available.
flex_basis: When used in a flex layout, specifies the initial size of the element.
align_self: Overrides the align_items property of a flex or grid container.
justify_self: Specifies how the element is justified inside a flex or grid container.
order: The layout order for the element within a flex or grid container.
grid_area: The name of the grid area to place the element in.
grid_row: The name of the grid row to place the element in.
grid_row_start: The name of the grid row to start the element in.
grid_row_end: The name of the grid row to end the element in.
grid_column: The name of the grid column to place the element in.
grid_column_start: The name of the grid column to start the element in.
grid_column_end: The name of the grid column to end the element in.
margin: The margin to apply around the element.
margin_top: The margin to apply above the element.
margin_bottom: The margin to apply below the element.
margin_start: The margin to apply before the element.
margin_end: The margin to apply after the element.
margin_x: The margin to apply to the left and right of the element.
margin_y: The margin to apply to the top and bottom of the element.
width: The width of the element.
height: The height of the element.
min_width: The minimum width of the element.
min_height: The minimum height of the element.
max_width: The maximum width of the element.
max_height: The maximum height of the element.
position: Specifies how the element is positioned.
top: The distance from the top of the containing element.
bottom: The distance from the bottom of the containing element.
start: The distance from the start of the containing element.
end: The distance from the end of the containing element.
left: The distance from the left of the containing element.
right: The distance from the right of the containing element.
z_index: The stack order of the element.
is_hidden: Whether the element is hidden.
UNSAFE_class_name: A CSS class to apply to the element.
UNSAFE_style: A CSS style to apply to the element.
"""
return spectrum_element(
"ActionButton",
*children,
type=type,
on_press=on_press,
on_press_start=on_press_start,
on_press_end=on_press_end,
on_press_up=on_press_up,
on_press_change=on_press_change,
on_focus=on_focus,
on_blur=on_blur,
on_focus_change=on_focus_change,
on_key_down=on_key_down,
on_key_up=on_key_up,
auto_focus=auto_focus,
is_disabled=is_disabled,
is_quiet=is_quiet,
static_color=static_color,
flex=flex,
flex_grow=flex_grow,
flex_shrink=flex_shrink,
flex_basis=flex_basis,
align_self=align_self,
justify_self=justify_self,
order=order,
grid_area=grid_area,
grid_row=grid_row,
grid_row_start=grid_row_start,
grid_row_end=grid_row_end,
grid_column=grid_column,
grid_column_start=grid_column_start,
grid_column_end=grid_column_end,
margin=margin,
margin_top=margin_top,
margin_bottom=margin_bottom,
margin_start=margin_start,
margin_end=margin_end,
margin_x=margin_x,
margin_y=margin_y,
width=width,
height=height,
min_width=min_width,
min_height=min_height,
max_width=max_width,
max_height=max_height,
position=position,
top=top,
bottom=bottom,
start=start,
end=end,
left=left,
right=right,
z_index=z_index,
is_hidden=is_hidden,
UNSAFE_class_name=UNSAFE_class_name,
UNSAFE_style=UNSAFE_style,
)
12 changes: 3 additions & 9 deletions plugins/ui/src/deephaven/ui/components/spectrum/basic.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
from __future__ import annotations
from typing import Any
from ...elements import BaseElement


def spectrum_element(name: str, /, *children, **props):
def spectrum_element(name: str, /, *children: Any, **props: Any) -> BaseElement:
"""
Base class for UI elements that are part of the Spectrum design system.
All names are automatically prefixed with "deephaven.ui.spectrum.", and all props are automatically camelCased.
"""
return BaseElement(f"deephaven.ui.spectrum.{name}", *children, **props)


def action_button(*children, **props):
"""
Python implementation for the Adobe React Spectrum ActionButton component.
https://react-spectrum.adobe.com/react-spectrum/ActionButton.html
"""
return spectrum_element("ActionButton", *children, **props)


def button(*children, **props):
"""
Python implementation for the Adobe React Spectrum Button component.
Expand Down
Loading

0 comments on commit 38b202f

Please sign in to comment.