mirror of
https://github.com/rio-labs/rio.git
synced 2025-12-16 18:25:45 -06:00
add event order control to KeyEventListener
This commit is contained in:
211
changelog.md
211
changelog.md
@@ -4,171 +4,172 @@
|
||||
|
||||
Changes:
|
||||
|
||||
- Components that depend on session attributes (like `active_page_url`) or
|
||||
- Components that depend on session attributes (like `active_page_url`) or
|
||||
session attachments are now automatically rebuilt when those values change
|
||||
- Components are now rebuilt immediately after a state change, not just after
|
||||
- Components are now rebuilt immediately after a state change, not just after
|
||||
event handlers
|
||||
- `TextStyle` no longer has default values, omitted values are left unchanged
|
||||
- `TextStyle` no longer has default values, omitted values are left unchanged
|
||||
instead
|
||||
|
||||
Deprecations:
|
||||
|
||||
- The `CursorStyle` enum is deprecated in favor of string literals
|
||||
Deprecations:
|
||||
- The `CursorStyle` enum is deprecated in favor of string literals
|
||||
|
||||
Bugfixes:
|
||||
|
||||
- Fix `rio.Revealer` not stretching its child
|
||||
- Fix various components propagating click events
|
||||
- Fix `rio.Revealer` not stretching its child
|
||||
- Fix various components propagating click events
|
||||
|
||||
Additions:
|
||||
|
||||
- `NumberInput` can now evaluate math expressions
|
||||
- `PointerEventListener` can now listen to only specific button events
|
||||
- `KeyEventListener` can now listen to only specifc hotkeys
|
||||
- `Calendar` now has a `is_sensitive` parameter
|
||||
- New component: `rio.PdfViewer`
|
||||
- `rio.Image` now has a loading animation
|
||||
- Added `accessibility_role` parameter to all components
|
||||
- Added 'accessibility_relationship' parameter to `Link` component
|
||||
- Added `auto_focus` parameter to input components
|
||||
- Added `Session.pick_folder`
|
||||
- Added `Font.from_google_fonts` and `Font.from_css_file`
|
||||
- Added `rio.HttpOnly`
|
||||
- Added `text_style` parameter to `rio.TextInput`
|
||||
- Experimental additions:
|
||||
- `rio.List`
|
||||
- `rio.Dict`
|
||||
- `rio.Set`
|
||||
- `rio.Dataclass`
|
||||
- `NumberInput` can now evaluate math expressions
|
||||
- `PointerEventListener` can now listen to only specific button events
|
||||
- `PointerEventListener` and `KeyEventListener` now support the `event_order`
|
||||
parameter to control whether events are handled before or after child
|
||||
components
|
||||
- `KeyEventListener` can now listen to only specifc hotkeys
|
||||
- `Calendar` now has a `is_sensitive` parameter
|
||||
- New component: `rio.PdfViewer`
|
||||
- `rio.Image` now has a loading animation
|
||||
- Added `accessibility_role` parameter to all components
|
||||
- Added 'accessibility_relationship' parameter to `Link` component
|
||||
- Added `auto_focus` parameter to input components
|
||||
- Added `Session.pick_folder`
|
||||
- Added `Font.from_google_fonts` and `Font.from_css_file`
|
||||
- Added `rio.HttpOnly`
|
||||
- Added `text_style` parameter to `rio.TextInput`
|
||||
- Experimental additions:
|
||||
- `rio.List`
|
||||
- `rio.Dict`
|
||||
- `rio.Set`
|
||||
- `rio.Dataclass`
|
||||
|
||||
## 0.11
|
||||
|
||||
- Added `tile` fill mode to `rio.ImageFill`
|
||||
- `Component.force_refresh` is now synchronous
|
||||
- Added `tile` fill mode to `rio.ImageFill`
|
||||
- Colors now use Oklab instead of RGB
|
||||
- Breaking: `rio.Color.hex` now returns a 6-digit hex code instead of an
|
||||
- Added `tile` fill mode to `rio.ImageFill`
|
||||
- `Component.force_refresh` is now synchronous
|
||||
- Added `tile` fill mode to `rio.ImageFill`
|
||||
- Colors now use Oklab instead of RGB
|
||||
- Breaking: `rio.Color.hex` now returns a 6-digit hex code instead of an
|
||||
8-digit one. Use `rio.Color.hexa` to get the old behavior.
|
||||
- Dialogs now apply a style by default
|
||||
- `rio.Drawer` now sizes itself to not only fit the anchor, but also the
|
||||
- Dialogs now apply a style by default
|
||||
- `rio.Drawer` now sizes itself to not only fit the anchor, but also the
|
||||
drawer content
|
||||
- `rio.Popup` now accepts `user_closable` and `modal`, just like dialogs
|
||||
- more styling options for cells in `rio.Table`
|
||||
- `rio.Popup` now accepts `user_closable` and `modal`, just like dialogs
|
||||
- more styling options for cells in `rio.Table`
|
||||
|
||||
- Expose additional platform information:
|
||||
- Expose additional platform information:
|
||||
|
||||
- `rio.Session.screen_width`
|
||||
- `rio.Session.screen_height`
|
||||
- `rio.Session.pixels_per_font_height`
|
||||
- `rio.Session.scroll_bar_size`
|
||||
- `rio.Session.primary_pointer_type`
|
||||
- `rio.Session.screen_width`
|
||||
- `rio.Session.screen_height`
|
||||
- `rio.Session.pixels_per_font_height`
|
||||
- `rio.Session.scroll_bar_size`
|
||||
- `rio.Session.primary_pointer_type`
|
||||
|
||||
- Themes now take an additional `header_font` parameter
|
||||
- Themes now take an additional `header_font` parameter
|
||||
|
||||
- Breaking: Gradient stops can now be specified just as colors and Rio will
|
||||
- Breaking: Gradient stops can now be specified just as colors and Rio will
|
||||
infer their position (breaking, because the stops must be ordered now)
|
||||
|
||||
- add icons for common brands
|
||||
- add icons for common brands
|
||||
|
||||
## ???
|
||||
|
||||
- New styles for input boxes: "rounded" and "pill"
|
||||
- Improved mobile support: Dragging is now much smoother
|
||||
- Improved tables
|
||||
- `rio run` now also works when using `as_fastapi`
|
||||
- New styles for input boxes: "rounded" and "pill"
|
||||
- Improved mobile support: Dragging is now much smoother
|
||||
- Improved tables
|
||||
- `rio run` now also works when using `as_fastapi`
|
||||
|
||||
## 0.10
|
||||
|
||||
- `rio.Dropdown` will now open a fullscreen popup on mobile devices
|
||||
- `rio.MediaPlayer` now also triggers the `on_playback_end` event when the
|
||||
- `rio.Dropdown` will now open a fullscreen popup on mobile devices
|
||||
- `rio.MediaPlayer` now also triggers the `on_playback_end` event when the
|
||||
video loops
|
||||
- experimental support for base-URL
|
||||
- dialogs!
|
||||
- dialogs can now store a result value similar to futures
|
||||
- `rio.Text.wrap` is now `rio.Text.overflow`. Same for markdown.
|
||||
- removed `rio.Popup.on_open_or_close`. This event never actually fired.
|
||||
- `rio.Link` can now optionally display an icon
|
||||
- Rio will automatically create basic navigation for you, if your app has more
|
||||
- experimental support for base-URL
|
||||
- dialogs!
|
||||
- dialogs can now store a result value similar to futures
|
||||
- `rio.Text.wrap` is now `rio.Text.overflow`. Same for markdown.
|
||||
- removed `rio.Popup.on_open_or_close`. This event never actually fired.
|
||||
- `rio.Link` can now optionally display an icon
|
||||
- Rio will automatically create basic navigation for you, if your app has more
|
||||
than one page
|
||||
- Updated button styles: Added `colored-text` and renamed `plain` ->
|
||||
- Updated button styles: Added `colored-text` and renamed `plain` ->
|
||||
`plain-text`
|
||||
- Methods for creating dialogs are now in `rio.Session` rather than
|
||||
- Methods for creating dialogs are now in `rio.Session` rather than
|
||||
`rio.Component`.
|
||||
- Page rework
|
||||
- Add `rio.Redirect`
|
||||
- Still missing automatic page scan
|
||||
- New experimental `rio.FilePickerArea` component
|
||||
- Page rework
|
||||
- Add `rio.Redirect`
|
||||
- Still missing automatic page scan
|
||||
- New experimental `rio.FilePickerArea` component
|
||||
|
||||
## 0.9.2
|
||||
|
||||
- restyled `rio.Switch`
|
||||
- New ~~experimental~~ broken component `AspectRatioContainer`
|
||||
- restyled `rio.Switch`
|
||||
- New ~~experimental~~ broken component `AspectRatioContainer`
|
||||
|
||||
## 0.9.1
|
||||
|
||||
- added gain_focus / lose_focus events to TextInput and NumberInput
|
||||
- `.rioignore` has been superseeded by the new `project-files` setting in
|
||||
- added gain_focus / lose_focus events to TextInput and NumberInput
|
||||
- `.rioignore` has been superseeded by the new `project-files` setting in
|
||||
`rio.toml`
|
||||
- values in `rio.toml` are now written in kebab-case instead of
|
||||
- values in `rio.toml` are now written in kebab-case instead of
|
||||
all_lower_case. Rio will still recognize the old names and automatically fix
|
||||
them for you.
|
||||
- deprecated `light` parameter of `Theme.from_color`, has been superseded by
|
||||
- deprecated `light` parameter of `Theme.from_color`, has been superseded by
|
||||
`mode`
|
||||
- Tooltips now default to `position="auto"`
|
||||
- Icons now use `_` instead of `-` in their names. This brings them more in line
|
||||
- Tooltips now default to `position="auto"`
|
||||
- Icons now use `_` instead of `-` in their names. This brings them more in line
|
||||
with Python naming conventions
|
||||
- Checkbox restyling
|
||||
- Checkbox restyling
|
||||
|
||||
## 0.9
|
||||
|
||||
- Buttons now have a smaller minimum size when using a `rio.Component` as
|
||||
- Buttons now have a smaller minimum size when using a `rio.Component` as
|
||||
content
|
||||
- `FrostedGlassFill` added (Contributed by MiniTT)
|
||||
- added `@rio.event.on_window_size_change`
|
||||
- popups now default to the "hud" color
|
||||
- popups and tooltips are no longer cut off by other components
|
||||
- Add HTML meta tags
|
||||
- Add functions for reading and writing clipboard contents to the `Session`
|
||||
- `FrostedGlassFill` added (Contributed by MiniTT)
|
||||
- added `@rio.event.on_window_size_change`
|
||||
- popups now default to the "hud" color
|
||||
- popups and tooltips are no longer cut off by other components
|
||||
- Add HTML meta tags
|
||||
- Add functions for reading and writing clipboard contents to the `Session`
|
||||
(Contributed by MiniTT)
|
||||
- The color of drawers is now configurable, and also sets the theme context
|
||||
- added `Calendar` component
|
||||
- added `DateInput` component
|
||||
- massive dev-tools overhaul
|
||||
- new (but experimental) `Switcher` component
|
||||
- TextInputs now update their text in real-time
|
||||
- `rio run` no longer opens a browser
|
||||
- `rio.HTML` components now execute embedded `<script>` nodes
|
||||
- added `Checkbox` Component
|
||||
- `FlowContainer` now has a convenience `spacing` parameter which controls both
|
||||
- The color of drawers is now configurable, and also sets the theme context
|
||||
- added `Calendar` component
|
||||
- added `DateInput` component
|
||||
- massive dev-tools overhaul
|
||||
- new (but experimental) `Switcher` component
|
||||
- TextInputs now update their text in real-time
|
||||
- `rio run` no longer opens a browser
|
||||
- `rio.HTML` components now execute embedded `<script>` nodes
|
||||
- added `Checkbox` Component
|
||||
- `FlowContainer` now has a convenience `spacing` parameter which controls both
|
||||
`row_spacing` and `column_spacing` at the same time
|
||||
|
||||
deprecations:
|
||||
|
||||
- `rio.Fill` and `rio.FillLike` deprecated. Most components only support
|
||||
- `rio.Fill` and `rio.FillLike` deprecated. Most components only support
|
||||
specific fills, so these have no purpose any more
|
||||
- `display_controls` parameter of `CodeBlock` component renamed to
|
||||
- `display_controls` parameter of `CodeBlock` component renamed to
|
||||
`show_controls`
|
||||
|
||||
breaking:
|
||||
|
||||
- `Text.justify` now defaults to `"left"`
|
||||
- `FlowContainer.justify` now defaults to `"left"`
|
||||
- `rio.Theme` is no longer frozen, and can now be modified. This is breaking,
|
||||
- `Text.justify` now defaults to `"left"`
|
||||
- `FlowContainer.justify` now defaults to `"left"`
|
||||
- `rio.Theme` is no longer frozen, and can now be modified. This is breaking,
|
||||
because the `replace` method has been removed
|
||||
|
||||
## 0.8
|
||||
|
||||
- Rectangles now honor the theme's shadow color
|
||||
- Renamed `Banner.markup` to `Banner.markdown`
|
||||
- Removed the "multiline" style from Banners
|
||||
- Removed `Button.initially_disabled_for`
|
||||
- Added a `text_color` parameter to `Theme.from_colors` and
|
||||
- Rectangles now honor the theme's shadow color
|
||||
- Renamed `Banner.markup` to `Banner.markdown`
|
||||
- Removed the "multiline" style from Banners
|
||||
- Removed `Button.initially_disabled_for`
|
||||
- Added a `text_color` parameter to `Theme.from_colors` and
|
||||
`Theme.pair_from_colors`
|
||||
- `rio run` now checks that the installed version of Rio is up-to-date
|
||||
- `rio run` now checks that the installed version of Rio is up-to-date
|
||||
|
||||
## 0.7
|
||||
|
||||
- New example: multi-page website
|
||||
- New component: CodeBlock
|
||||
- UserSettings can now have mutable default values
|
||||
- Removed "undefined space"
|
||||
- New example: multi-page website
|
||||
- New component: CodeBlock
|
||||
- UserSettings can now have mutable default values
|
||||
- Removed "undefined space"
|
||||
|
||||
@@ -697,12 +697,15 @@ export type KeyEventListenerState = KeyboardFocusableComponentState & {
|
||||
reportKeyDown: KeyCombination[] | true;
|
||||
reportKeyUp: KeyCombination[] | true;
|
||||
reportKeyPress: KeyCombination[] | true;
|
||||
event_order: "before-child" | "after-child";
|
||||
};
|
||||
|
||||
export class KeyEventListenerComponent extends KeyboardFocusableComponent<KeyEventListenerState> {
|
||||
private keyDownCombinations: Set<string> | true;
|
||||
private keyUpCombinations: Set<string> | true;
|
||||
private keyPressCombinations: Set<string> | true;
|
||||
private onKeyDownBound: ((e: KeyboardEvent) => void) | null = null;
|
||||
private onKeyUpBound: ((e: KeyboardEvent) => void) | null = null;
|
||||
|
||||
createElement(context: ComponentStatesUpdateContext): HTMLElement {
|
||||
let element = document.createElement("div");
|
||||
@@ -738,30 +741,54 @@ export class KeyEventListenerComponent extends KeyboardFocusableComponent<KeyEve
|
||||
);
|
||||
}
|
||||
|
||||
let reportKeyDown =
|
||||
this.keyDownCombinations === true ||
|
||||
this.keyDownCombinations.size > 0;
|
||||
let reportKeyUp =
|
||||
this.keyUpCombinations === true || this.keyUpCombinations.size > 0;
|
||||
let reportKeyPress =
|
||||
this.keyPressCombinations === true ||
|
||||
this.keyPressCombinations.size > 0;
|
||||
// Check if we need to update event listeners
|
||||
if (
|
||||
deltaState.reportKeyDown !== undefined ||
|
||||
deltaState.reportKeyPress !== undefined ||
|
||||
deltaState.event_order !== undefined
|
||||
) {
|
||||
let reportKeyDown =
|
||||
this.keyDownCombinations === true ||
|
||||
this.keyDownCombinations.size > 0;
|
||||
let reportKeyPress =
|
||||
this.keyPressCombinations === true ||
|
||||
this.keyPressCombinations.size > 0;
|
||||
let eventOrder = deltaState.event_order ?? this.state.event_order;
|
||||
|
||||
if (reportKeyDown || reportKeyPress) {
|
||||
this.element.onkeydown = (e: KeyboardEvent) => {
|
||||
this.handleKeyEvent(e, "KeyPress", this.keyPressCombinations);
|
||||
this.handleKeyEvent(e, "KeyDown", this.keyDownCombinations);
|
||||
};
|
||||
} else {
|
||||
this.element.onkeydown = null;
|
||||
this.onKeyDownBound = this._updateEventListener(
|
||||
"keydown",
|
||||
reportKeyDown || reportKeyPress,
|
||||
eventOrder,
|
||||
this.onKeyDownBound,
|
||||
(e: KeyboardEvent) => {
|
||||
this.handleKeyEvent(
|
||||
e,
|
||||
"KeyPress",
|
||||
this.keyPressCombinations
|
||||
);
|
||||
this.handleKeyEvent(e, "KeyDown", this.keyDownCombinations);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (reportKeyUp) {
|
||||
this.element.onkeyup = (e: KeyboardEvent) => {
|
||||
this.handleKeyEvent(e, "KeyUp", this.keyUpCombinations);
|
||||
};
|
||||
} else {
|
||||
this.element.onkeyup = null;
|
||||
if (
|
||||
deltaState.reportKeyUp !== undefined ||
|
||||
deltaState.event_order !== undefined
|
||||
) {
|
||||
let reportKeyUp =
|
||||
this.keyUpCombinations === true ||
|
||||
this.keyUpCombinations.size > 0;
|
||||
let eventOrder = deltaState.event_order ?? this.state.event_order;
|
||||
|
||||
this.onKeyUpBound = this._updateEventListener(
|
||||
"keyup",
|
||||
reportKeyUp,
|
||||
eventOrder,
|
||||
this.onKeyUpBound,
|
||||
(e: KeyboardEvent) => {
|
||||
this.handleKeyEvent(e, "KeyUp", this.keyUpCombinations);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.replaceOnlyChild(context, deltaState.content);
|
||||
@@ -804,6 +831,32 @@ export class KeyEventListenerComponent extends KeyboardFocusableComponent<KeyEve
|
||||
...encodedEvent,
|
||||
});
|
||||
}
|
||||
|
||||
/// Helper method to manage event listeners with capture phase support
|
||||
private _updateEventListener(
|
||||
eventName: string,
|
||||
shouldInstall: boolean,
|
||||
eventOrder: "before-child" | "after-child",
|
||||
currentHandler: ((e: KeyboardEvent) => void) | null,
|
||||
callbackMethod: (e: KeyboardEvent) => void
|
||||
): ((e: KeyboardEvent) => void) | null {
|
||||
// Remove existing listener if it exists
|
||||
if (currentHandler !== null) {
|
||||
this.element.removeEventListener(eventName, currentHandler, {
|
||||
capture: this.state.event_order === "before-child",
|
||||
});
|
||||
}
|
||||
|
||||
if (!shouldInstall) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Install new listener with current capture setting
|
||||
this.element.addEventListener(eventName, callbackMethod, {
|
||||
capture: eventOrder === "before-child",
|
||||
});
|
||||
return callbackMethod;
|
||||
}
|
||||
}
|
||||
|
||||
function keyCombinationsSetFromDeltaState(
|
||||
|
||||
@@ -679,6 +679,11 @@ class KeyEventListener(KeyboardFocusableFundamentalComponent):
|
||||
`on_key_up`: A function to call when a key is released.
|
||||
|
||||
`on_key_press`: A function to call repeatedly while a key is held down.
|
||||
|
||||
`event_order`: Controls when this listener receives events relative to
|
||||
its child components. When `"before-child"`, this listener's handlers
|
||||
are called before any child component handlers. When `"after-child"`
|
||||
(default), child components receive events first, then this listener.
|
||||
"""
|
||||
|
||||
content: rio.Component
|
||||
@@ -695,6 +700,7 @@ class KeyEventListener(KeyboardFocusableFundamentalComponent):
|
||||
rio.EventHandler[KeyPressEvent]
|
||||
| t.Mapping[KeyCombination, rio.EventHandler[KeyPressEvent]]
|
||||
) = None
|
||||
event_order: t.Literal["before-child", "after-child"] = "after-child"
|
||||
|
||||
def __post_init__(self):
|
||||
# TODO: These values are never updated, which is a problem if someone
|
||||
|
||||
Reference in New Issue
Block a user