mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-28 16:29:46 -06:00
improved sliders, textinput and layout explanations
This commit is contained in:
@@ -54,19 +54,25 @@ export class SliderComponent extends ComponentBase {
|
||||
return this.state.value;
|
||||
}
|
||||
|
||||
// Calculate the value as a fraction of the track width
|
||||
// Calculate the selected value from the event coordinates
|
||||
let rect = this.innerElement.getBoundingClientRect();
|
||||
let fraction = (event.clientX - rect.left) / rect.width;
|
||||
fraction = Math.max(0, Math.min(1, fraction));
|
||||
|
||||
// Enforce the step size
|
||||
let valueRange = this.state.maximum - this.state.minimum;
|
||||
let value = fraction * valueRange + this.state.minimum;
|
||||
|
||||
// Enforce the step size
|
||||
//
|
||||
// Converting to a value, and back to fractions may seem convoluted, but
|
||||
// this ensures that the step size is enforced correctly. Careless math
|
||||
// can lead to floating point errors that cause the reported value to be
|
||||
// off by a little (e.g. 7.99999999 instead of 8).
|
||||
if (this.state.step !== 0) {
|
||||
let normalizedStepSize = this.state.step / valueRange;
|
||||
let stepIndex = Math.round(value / this.state.step);
|
||||
value = stepIndex * this.state.step;
|
||||
|
||||
fraction =
|
||||
Math.round(fraction / normalizedStepSize) * normalizedStepSize;
|
||||
fraction = (value - this.state.minimum) / valueRange;
|
||||
}
|
||||
|
||||
// Move the knob
|
||||
@@ -76,7 +82,7 @@ export class SliderComponent extends ComponentBase {
|
||||
);
|
||||
|
||||
// Return the new value
|
||||
return fraction * valueRange + this.state.minimum;
|
||||
return value;
|
||||
}
|
||||
|
||||
private onDragStart(event: MouseEvent): boolean {
|
||||
|
||||
@@ -82,11 +82,15 @@ export class TextInputComponent extends ComponentBase {
|
||||
// date value.
|
||||
this.inputElement.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter') {
|
||||
// Update the state
|
||||
this.state.text = this.inputElement.value;
|
||||
|
||||
this.onChangeLimiter.call(this.state.text);
|
||||
this.onChangeLimiter.flush();
|
||||
// There is no need for the debouncer to report this call, since
|
||||
// Python will already trigger both change & confirm events when
|
||||
// it receives the message that is about to be sent.
|
||||
this.onChangeLimiter.clear();
|
||||
|
||||
// Inform the backend
|
||||
this.sendMessageToBackend({
|
||||
text: this.state.text,
|
||||
});
|
||||
|
||||
@@ -119,4 +119,15 @@ export class Debouncer {
|
||||
this.mostRecentPerformedCall = Date.now();
|
||||
this.pendingArguments = null;
|
||||
}
|
||||
|
||||
/// Clears any pending calls, ensuring that the debouncer will not call the
|
||||
/// function in the future unless `call` is invoked again.
|
||||
public clear(): void {
|
||||
this.pendingArguments = null;
|
||||
|
||||
if (this.timeout !== null) {
|
||||
clearTimeout(this.timeout);
|
||||
this.timeout = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,11 +187,13 @@ class TextInput(KeyboardFocusableFundamentalComponent):
|
||||
|
||||
self._apply_delta_state_from_frontend({"text": msg["text"]})
|
||||
|
||||
# Trigger both the change event...
|
||||
await self.call_event_handler(
|
||||
self.on_change,
|
||||
TextInputChangeEvent(self.text),
|
||||
)
|
||||
|
||||
# And the confirm event
|
||||
await self.call_event_handler(
|
||||
self.on_confirm,
|
||||
TextInputConfirmEvent(self.text),
|
||||
|
||||
@@ -17,7 +17,33 @@ import rio.data_models
|
||||
# - revealer
|
||||
# - scroll_container
|
||||
# - scroll_target
|
||||
# - slideshow
|
||||
|
||||
|
||||
# These components pass on the entirety of the available space to their
|
||||
# children
|
||||
FULL_SIZE_SINGLE_CONTAINERS: set[type[rio.Component]] = {
|
||||
rio.Button,
|
||||
rio.Card,
|
||||
rio.Container,
|
||||
rio.CustomListItem,
|
||||
rio.KeyEventListener,
|
||||
rio.Link,
|
||||
rio.MouseEventListener,
|
||||
rio.PageView,
|
||||
rio.Rectangle,
|
||||
rio.Slideshow,
|
||||
rio.Stack,
|
||||
rio.Switcher,
|
||||
}
|
||||
|
||||
|
||||
# These components make use of the `"grow"` value with `width`, `height` or
|
||||
# both.
|
||||
CONTAINERS_SUPPORTING_GROW: Iterable[type[rio.Component]] = {
|
||||
rio.Column,
|
||||
rio.Grid,
|
||||
rio.Row,
|
||||
}
|
||||
|
||||
|
||||
class LayoutExplainer:
|
||||
@@ -124,9 +150,11 @@ class LayoutExplainer:
|
||||
if axis == "width":
|
||||
parent_allocated_space = self._layout.parent_allocated_width
|
||||
parent_natural_size = self._layout.parent_natural_width
|
||||
specified_size = self.component.width
|
||||
else:
|
||||
parent_allocated_space = self._layout.parent_allocated_height
|
||||
parent_natural_size = self._layout.parent_natural_height
|
||||
specified_size = self.component.height
|
||||
|
||||
parent_class_name = type(parent).__name__
|
||||
|
||||
@@ -135,19 +163,7 @@ class LayoutExplainer:
|
||||
return f"This is the app's top-level component. As such, the {target_class_name} was allocated the full a {axis} of {allocated_space_before_alignment:.1f} available in the window."
|
||||
|
||||
# Single container?
|
||||
if type(parent) in (
|
||||
rio.Button,
|
||||
rio.Card,
|
||||
rio.Container,
|
||||
rio.CustomListItem,
|
||||
rio.KeyEventListener,
|
||||
rio.MouseEventListener,
|
||||
rio.Link,
|
||||
rio.PageView,
|
||||
rio.Rectangle,
|
||||
rio.Stack,
|
||||
rio.Switcher,
|
||||
):
|
||||
if type(parent) in FULL_SIZE_SINGLE_CONTAINERS:
|
||||
return f"Because `{parent_class_name}` components pass on all available space to their children, the component's {axis} is the full {allocated_space_before_alignment:.1f} units available in its parent."
|
||||
|
||||
# Overlay
|
||||
@@ -163,6 +179,11 @@ class LayoutExplainer:
|
||||
or isinstance(parent, rio.Column)
|
||||
and axis == "width"
|
||||
):
|
||||
if specified_size == "grow":
|
||||
self.warnings.append(
|
||||
f'The component has `{axis}="grow"` set, but it is placed inside of a `{parent_class_name}`. Because {parent_class_name}s pass on the entire available space in this direction to all children it has no effect.'
|
||||
)
|
||||
|
||||
return f"The component is placed inside of a {parent_class_name}. Since all children of {parent_class_name}s receive the full {axis}, it has received the entire {allocated_space_before_alignment:.1f} units available in its parent."
|
||||
|
||||
# Major axis
|
||||
@@ -236,6 +257,11 @@ class LayoutExplainer:
|
||||
allocated the space it was, in a single direction.
|
||||
"""
|
||||
# Prepare some values based on axis
|
||||
try:
|
||||
parent = self.session._weak_components_by_id[self._layout.parent_id]
|
||||
except KeyError:
|
||||
parent = None
|
||||
|
||||
if axis == "width":
|
||||
allocated_space = self._layout.allocated_width
|
||||
allocated_space_before_alignment = (
|
||||
@@ -269,6 +295,17 @@ class LayoutExplainer:
|
||||
|
||||
target_class_name = type(self.component).__name__
|
||||
|
||||
# Warn if the component has a `grow` attribute set, but is placed inside
|
||||
# of a container that doesn't support it
|
||||
if (
|
||||
specified_size == "grow"
|
||||
and parent is not None
|
||||
and type(parent) not in CONTAINERS_SUPPORTING_GROW
|
||||
):
|
||||
self.warnings.append(
|
||||
f'The component has `{axis}="grow"` set, but it is placed inside of a `{type(parent).__name__}`. {type(parent).__name__} components can not make use of this property, so it has no effect.'
|
||||
)
|
||||
|
||||
# How much space did the parent hand down?
|
||||
result = io.StringIO()
|
||||
result.write(
|
||||
|
||||
Reference in New Issue
Block a user