mirror of
https://github.com/rio-labs/rio.git
synced 2026-01-27 07:48:44 -06:00
switchers now fade between components, are documented and public
This commit is contained in:
@@ -3,12 +3,12 @@ import { ComponentBase, ComponentState } from './componentBase';
|
||||
import { componentsById } from '../componentManagement';
|
||||
import { LayoutContext, updateLayout } from '../layouting';
|
||||
import { easeInOut } from '../easeFunctions';
|
||||
|
||||
const TRANSITION_TIME: number = 0.35;
|
||||
import { commitCss } from '../utils';
|
||||
|
||||
export type SwitcherState = ComponentState & {
|
||||
_type_: 'Switcher-builtin';
|
||||
content?: ComponentId | null;
|
||||
transition_time?: number;
|
||||
};
|
||||
|
||||
export class SwitcherComponent extends ComponentBase {
|
||||
@@ -39,6 +39,7 @@ export class SwitcherComponent extends ComponentBase {
|
||||
createElement(): HTMLElement {
|
||||
let element = document.createElement('div');
|
||||
element.classList.add('rio-switcher');
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@@ -46,6 +47,15 @@ export class SwitcherComponent extends ComponentBase {
|
||||
deltaState: SwitcherState,
|
||||
latentComponents: Set<ComponentBase>
|
||||
): void {
|
||||
// Update the transition time first, in case the code below is about
|
||||
// to start an animation.
|
||||
if (deltaState.transition_time !== undefined) {
|
||||
this.element.style.setProperty(
|
||||
'--rio-switcher-transition-time',
|
||||
`${deltaState.transition_time}s`
|
||||
);
|
||||
}
|
||||
|
||||
// Update the child
|
||||
if (
|
||||
!this.isInitialized ||
|
||||
@@ -56,13 +66,41 @@ export class SwitcherComponent extends ComponentBase {
|
||||
|
||||
// Out with the old
|
||||
if (this.activeChildContainer !== null) {
|
||||
// The old component may be used somewhere else in the UI, so
|
||||
// the switcher can't rely on it still being available. To get
|
||||
// around this, create a copy of the element's HTML tree and use
|
||||
// that for the animation.
|
||||
//
|
||||
// Moreover, teh component may have already been removed from
|
||||
// the switcher. This can happen when it was moved into another
|
||||
// component. Thus, fetch the component by its id, rather than
|
||||
// using the contained HTML node.
|
||||
let oldComponent = componentsById[this.state.content!]!;
|
||||
let oldElementClone = oldComponent.element.cloneNode(
|
||||
true
|
||||
) as HTMLElement;
|
||||
|
||||
// Discard the old component
|
||||
this.replaceOnlyChild(
|
||||
latentComponents,
|
||||
null,
|
||||
this.activeChildContainer
|
||||
);
|
||||
this.activeChildContainer.remove();
|
||||
|
||||
// Animate out the old component
|
||||
this.activeChildContainer.appendChild(oldElementClone);
|
||||
this.activeChildContainer.classList.remove(
|
||||
'rio-switcher-active'
|
||||
);
|
||||
|
||||
// Make sure to remove the child after the animation finishes
|
||||
let oldChildContainer = this.activeChildContainer;
|
||||
|
||||
setTimeout(() => {
|
||||
oldChildContainer.remove();
|
||||
}, this.state.transition_time * 1000);
|
||||
|
||||
// No more children :(
|
||||
this.activeChildContainer = null;
|
||||
this.activeChildInstance = null;
|
||||
}
|
||||
@@ -86,6 +124,10 @@ export class SwitcherComponent extends ComponentBase {
|
||||
|
||||
// Remember the child, as it is needed frequently
|
||||
this.activeChildInstance = componentsById[deltaState.content!]!;
|
||||
|
||||
// Animate the child in
|
||||
commitCss(this.activeChildContainer);
|
||||
this.activeChildContainer.classList.add('rio-switcher-active');
|
||||
}
|
||||
|
||||
// Start the layouting process
|
||||
@@ -142,7 +184,7 @@ export class SwitcherComponent extends ComponentBase {
|
||||
let now = Date.now();
|
||||
let linearT = Math.min(
|
||||
1,
|
||||
(now - this.animationStartedAt) / 1000 / TRANSITION_TIME
|
||||
(now - this.animationStartedAt) / 1000 / this.state.transition_time
|
||||
);
|
||||
let easedT = easeInOut(linearT);
|
||||
|
||||
|
||||
@@ -2372,6 +2372,16 @@ textarea:not(:placeholder-shown) ~ .rio-input-box-label,
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.rio-switcher > * {
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity var(--rio-switcher-transition-time) ease-in-out;
|
||||
}
|
||||
|
||||
.rio-switcher-active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
// Tooltip
|
||||
.rio-tooltip {
|
||||
pointer-events: none;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import KW_ONLY
|
||||
from typing import final
|
||||
|
||||
import rio
|
||||
@@ -14,22 +15,41 @@ __all__ = [
|
||||
@final
|
||||
class Switcher(FundamentalComponent):
|
||||
"""
|
||||
TODO
|
||||
Smoothly transitions between components.
|
||||
|
||||
The `Switcher` component is a container which can display one component at a
|
||||
time. What makes it useful, is that when you change the `content` attribute,
|
||||
rather than instantly swapping the displayed component, it will smoothly
|
||||
transition between the two.
|
||||
|
||||
Moreover, whenever the content's size changes, the `Switcher` will
|
||||
smoothly resize to match the new size. This means you can use switchers
|
||||
to smoothly transition between components of different sizes.
|
||||
|
||||
`content` may also be `None`, in which case the `Switcher` won't display
|
||||
anything. This in turn allows you to animate the appearance or disappearance
|
||||
of a component, e.g. for a sidebar.
|
||||
|
||||
|
||||
## Attributes
|
||||
|
||||
`content`: The currently displayed component.
|
||||
`content`: The component to display inside the switcher. If `None`, the
|
||||
switcher will be empty.
|
||||
|
||||
`transition_time`: How many seconds it should take for the switcher to
|
||||
transition between components and sizes.
|
||||
|
||||
|
||||
## Metadata
|
||||
|
||||
`public`: False
|
||||
|
||||
`experimental`: True
|
||||
"""
|
||||
|
||||
content: rio.Component | None
|
||||
|
||||
_: KW_ONLY
|
||||
|
||||
transition_time: float = 0.35
|
||||
|
||||
|
||||
Switcher._unique_id = "Switcher-builtin"
|
||||
|
||||
@@ -10,7 +10,7 @@ class AssetError(Exception):
|
||||
access a nonexistent asset.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(message)
|
||||
|
||||
@property
|
||||
@@ -40,7 +40,7 @@ class ClipboardError(Exception):
|
||||
Exception raised for errors related to clipboard operations.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str):
|
||||
def __init__(self, message: str) -> None:
|
||||
super().__init__(message)
|
||||
|
||||
@property
|
||||
|
||||
@@ -103,7 +103,7 @@ class LinearGradientFill(Fill):
|
||||
self,
|
||||
*stops: tuple[Color, float],
|
||||
angle_degrees: float = 0.0,
|
||||
):
|
||||
) -> None:
|
||||
# Make sure there's at least one stop
|
||||
if not stops:
|
||||
raise ValueError("Gradients must have at least 1 stop")
|
||||
@@ -158,7 +158,7 @@ class ImageFill(Fill):
|
||||
image: ImageLike,
|
||||
*,
|
||||
fill_mode: Literal["fit", "stretch", "zoom"] = "fit",
|
||||
):
|
||||
) -> None:
|
||||
"""
|
||||
## Parameters
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ __all__ = [
|
||||
# input class has been created, some forward references may not be evaluatable
|
||||
# yet.
|
||||
class get_local_annotations(Mapping[str, introspection.types.TypeAnnotation]):
|
||||
def __init__(self, cls: type, *, strict: bool = False):
|
||||
def __init__(self, cls: type, *, strict: bool = False) -> None:
|
||||
# Note: Don't use `typing.get_type_hints` because it has a stupid bug in
|
||||
# python 3.10 where it dies if something is annotated as
|
||||
# `dataclasses.KW_ONLY`.
|
||||
|
||||
@@ -12,7 +12,7 @@ T = TypeVar("T")
|
||||
|
||||
|
||||
class SessionAttachments:
|
||||
def __init__(self, sess: session.Session):
|
||||
def __init__(self, sess: session.Session) -> None:
|
||||
self._session = sess
|
||||
self._attachments: dict[type, object] = {}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user