From 6c26854e80bfb53c25e3693bdedc65256972f383 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 14 Oct 2024 18:15:30 -0700 Subject: [PATCH] improve color handling --- reflex/vars/base.py | 28 ++++- reflex/vars/function.py | 1 + reflex/vars/number.py | 17 ++- reflex/vars/sequence.py | 126 ++++++++++++++++++++- tests/units/components/core/test_colors.py | 4 +- 5 files changed, 165 insertions(+), 11 deletions(-) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 07afecf8d..54f4ac454 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -644,6 +644,18 @@ class Var(Generic[VAR_TYPE]): return self + @overload + def guess_type(self: Var[str]) -> StringVar: ... + + @overload + def guess_type(self: Var[bool]) -> BooleanVar: ... + + @overload + def guess_type(self: Var[int] | Var[float] | Var[int | float]) -> NumberVar: ... + + @overload + def guess_type(self) -> Self: ... + def guess_type(self) -> Var: """Guesses the type of the variable based on its `_var_type` attribute. @@ -908,16 +920,20 @@ class Var(Generic[VAR_TYPE]): """ return ~self.bool() - def to_string(self): + def to_string(self, use_json: bool = True) -> StringVar: """Convert the var to a string. Returns: The string var. """ - from .function import JSON_STRINGIFY + from .function import JSON_STRINGIFY, OBJECT_PROTOTYPE_TO_STRING from .sequence import StringVar - return JSON_STRINGIFY.call(self).to(StringVar) + return ( + JSON_STRINGIFY.call(self).to(StringVar) + if use_json + else OBJECT_PROTOTYPE_TO_STRING.call(self).to(StringVar) + ) def as_ref(self) -> Var: """Get a reference to the var. @@ -1419,6 +1435,12 @@ def var_operation( ) -> Callable[P, ObjectVar[OBJECT_TYPE]]: ... +@overload +def var_operation( + func: Callable[P, CustomVarOperationReturn[T]], +) -> Callable[P, Var[T]]: ... + + def var_operation( func: Callable[P, CustomVarOperationReturn[T]], ) -> Callable[P, Var[T]]: diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 41a8dc34a..dea9ccac4 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -180,3 +180,4 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar): JSON_STRINGIFY = FunctionStringVar.create("JSON.stringify") +OBJECT_PROTOTYPE_TO_STRING = FunctionStringVar.create("Object.prototype.toString") diff --git a/reflex/vars/number.py b/reflex/vars/number.py index 9c665ff36..77c728d13 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -11,6 +11,7 @@ from typing import ( Any, Callable, NoReturn, + Type, TypeVar, Union, overload, @@ -1110,8 +1111,12 @@ def boolify(value: Var): ) +T = TypeVar("T") +U = TypeVar("U") + + @var_operation -def ternary_operation(condition: BooleanVar, if_true: Var, if_false: Var): +def ternary_operation(condition: BooleanVar, if_true: Var[T], if_false: Var[U]): """Create a ternary operation. Args: @@ -1122,10 +1127,14 @@ def ternary_operation(condition: BooleanVar, if_true: Var, if_false: Var): Returns: The ternary operation. """ - return var_operation_return( - js_expression=f"({condition} ? {if_true} : {if_false})", - var_type=unionize(if_true._var_type, if_false._var_type), + type_value: Union[Type[T], Type[U]] = unionize( + if_true._var_type, if_false._var_type ) + value: CustomVarOperationReturn[Union[T, U]] = var_operation_return( + js_expression=f"({condition} ? {if_true} : {if_false})", + var_type=type_value, + ) + return value NUMBER_TYPES = (int, float, NumberVar) diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index ee329dc50..c4e94e654 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -18,13 +18,15 @@ from typing import ( Set, Tuple, Type, - TypeVar, Union, overload, ) +from typing_extensions import TypeVar + from reflex import constants from reflex.constants.base import REFLEX_VAR_OPENING_TAG +from reflex.constants.colors import Color from reflex.utils.exceptions import VarTypeError from reflex.utils.types import GenericType, get_origin @@ -44,16 +46,20 @@ from .base import ( ) from .number import ( BooleanVar, + LiteralBooleanVar, LiteralNumberVar, NumberVar, raise_unsupported_operand_types, + ternary_operation, ) if TYPE_CHECKING: from .object import ObjectVar +STRING_TYPE = TypeVar("STRING_TYPE", default=str) -class StringVar(Var[str], python_types=str): + +class StringVar(Var[STRING_TYPE], python_types=str): """Base class for immutable string vars.""" @overload @@ -1625,3 +1631,119 @@ def array_concat_operation( js_expression=f"[...{lhs}, ...{rhs}]", var_type=Union[lhs._var_type, rhs._var_type], ) + + +class ColorVar(StringVar[Color], python_types=Color): + """Base class for immutable color vars.""" + + +@dataclasses.dataclass( + eq=False, + frozen=True, + **{"slots": True} if sys.version_info >= (3, 10) else {}, +) +class LiteralColorVar(CachedVarOperation, LiteralVar, ColorVar): + """Base class for immutable literal color vars.""" + + _var_value: Color = dataclasses.field(default_factory=lambda: Color(color="black")) + + @classmethod + def create( + cls, + value: Color, + _var_type: Type[Color] | None = None, + _var_data: VarData | None = None, + ) -> ColorVar: + """Create a var from a string value. + + Args: + value: The value to create the var from. + _var_type: The type of the var. + _var_data: Additional hooks and imports associated with the Var. + + Returns: + The var. + """ + return cls( + _js_expr="", + _var_type=_var_type or Color, + _var_data=_var_data, + _var_value=value, + ) + + def __hash__(self) -> int: + """Get the hash of the var. + + Returns: + The hash of the var. + """ + return hash( + ( + self.__class__.__name__, + self._var_value.color, + self._var_value.alpha, + self._var_value.shade, + ) + ) + + @cached_property_no_lock + def _cached_var_name(self) -> str: + """The name of the var. + + Returns: + The name of the var. + """ + alpha = self._var_value.alpha + alpha = ( + ternary_operation( + LiteralBooleanVar.create(alpha), + LiteralStringVar.create("a"), + LiteralStringVar.create("False"), + ) + if isinstance(alpha, Var) + else LiteralStringVar.create("a" if alpha else "") + ) + + shade = self._var_value.shade + shade = ( + shade.to_string(use_json=False) + if isinstance(shade, Var) + else LiteralStringVar.create(str(shade)) + ) + return str( + ConcatVarOperation.create( + LiteralStringVar.create("var(--"), + self._var_value.color, + LiteralStringVar.create("-"), + alpha, + shade, + LiteralStringVar.create(")"), + ) + ) + + @cached_property_no_lock + def _cached_get_all_var_data(self) -> VarData | None: + """Get all the var data. + + Returns: + The var data. + """ + return VarData.merge( + *[ + LiteralVar.create(var)._get_all_var_data() + for var in ( + self._var_value.color, + self._var_value.alpha, + self._var_value.shade, + ) + ], + self._var_data, + ) + + def json(self) -> str: + """Get the JSON representation of the var. + + Returns: + The JSON representation of the var. + """ + return json.dumps(f"{self._var_value}") diff --git a/tests/units/components/core/test_colors.py b/tests/units/components/core/test_colors.py index a6175d56a..0246f8006 100644 --- a/tests/units/components/core/test_colors.py +++ b/tests/units/components/core/test_colors.py @@ -31,7 +31,7 @@ def create_color_var(color): (create_color_var(rx.color("mint", 3, True)), '"var(--mint-a3)"', Color), ( create_color_var(rx.color(ColorState.color, ColorState.shade)), # type: ignore - f'("var(--"+{str(color_state_name)}.color+"-"+{str(color_state_name)}.shade+")")', + f'("var(--"+{str(color_state_name)}.color+"-"+(Object.prototype.toString({str(color_state_name)}.shade))+")")', Color, ), ( @@ -43,7 +43,7 @@ def create_color_var(color): create_color_var( rx.color(f"{ColorState.color_part}ato", f"{ColorState.shade}") # type: ignore ), - f'("var(--"+{str(color_state_name)}.color_part+"ato-"+{str(color_state_name)}.shade+")")', + f'("var(--"+({str(color_state_name)}.color_part+"ato")+"-"+{str(color_state_name)}.shade+")")', Color, ), (