Fixed command id collision. Added class support in style preset
This commit is contained in:
@@ -47,15 +47,16 @@ class Command:
|
||||
# In this situation,
|
||||
# either there is no parameter (so one single instance of the command is enough)
|
||||
# or the parameter is a kwargs (so the parameters are provided when the command is called)
|
||||
if (key is None
|
||||
and owner is not None
|
||||
and args is None # args is not provided
|
||||
):
|
||||
key = f"{owner.get_full_id()}-{name}"
|
||||
|
||||
key = key.replace("#{args}", _compute_from_args())
|
||||
key = key.replace("#{id}", owner.get_full_id())
|
||||
key = key.replace("#{id-name-args}", f"{owner.get_full_id()}-{name}-{_compute_from_args()}")
|
||||
if key is None:
|
||||
if owner is not None and args is None: # args is not provided
|
||||
key = f"{owner.get_full_id()}-{name}"
|
||||
else:
|
||||
key = f"{name}-{_compute_from_args()}"
|
||||
else:
|
||||
key = key.replace("#{args}", _compute_from_args())
|
||||
if owner is not None:
|
||||
key = key.replace("#{id}", owner.get_full_id())
|
||||
key = key.replace("#{id-name-args}", f"{owner.get_full_id()}-{name}-{_compute_from_args()}")
|
||||
|
||||
return key
|
||||
|
||||
@@ -78,24 +79,17 @@ class Command:
|
||||
self._bindings = []
|
||||
self._ft = None
|
||||
self._callback_parameters = dict(inspect.signature(callback).parameters) if callback else {}
|
||||
self._key = key
|
||||
|
||||
# special management when kwargs are provided
|
||||
# In this situation,
|
||||
# either there is no parameter (so one single instance of the command is enough)
|
||||
# or the parameter is a kwargs (so the parameters are provided when the command is called)
|
||||
if (self._key is None
|
||||
and self.owner is not None
|
||||
and args is None # args is not provided
|
||||
):
|
||||
self._key = f"{owner.get_full_id()}-{name}"
|
||||
self._key = self.process_key(key, self.name, self.owner, self.default_args, self.default_kwargs)
|
||||
|
||||
# register the command
|
||||
if auto_register:
|
||||
if self._key in CommandsManager.commands_by_key:
|
||||
self.id = CommandsManager.commands_by_key[self._key].id
|
||||
if self._key is not None:
|
||||
if self._key in CommandsManager.commands_by_key:
|
||||
self.id = CommandsManager.commands_by_key[self._key].id
|
||||
else:
|
||||
CommandsManager.register(self)
|
||||
else:
|
||||
CommandsManager.register(self)
|
||||
logger.warning(f"Command {self.name} has no key, it will not be registered.")
|
||||
|
||||
def get_key(self):
|
||||
return self._key
|
||||
|
||||
@@ -105,13 +105,13 @@ class FormattingCompletionEngine(BaseCompletionEngine):
|
||||
|
||||
case Context.CELL_ROW:
|
||||
return self._get_row_index_suggestions()
|
||||
|
||||
|
||||
case Context.TABLE_NAME:
|
||||
return self._get_table_name_suggestion()
|
||||
|
||||
|
||||
case Context.TABLES_SCOPE:
|
||||
return [Suggestion(":", "Define global rules for all tables", "syntax")]
|
||||
|
||||
|
||||
# =================================================================
|
||||
# Rule-level contexts
|
||||
# =================================================================
|
||||
@@ -236,13 +236,13 @@ class FormattingCompletionEngine(BaseCompletionEngine):
|
||||
except Exception:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
def _get_table_name_suggestion(self) -> list[Suggestion]:
|
||||
"""Get table name suggestion (current table only)."""
|
||||
if self.table_name:
|
||||
return [Suggestion(f'"{self.table_name}"', f"Current table: {self.table_name}", "table")]
|
||||
return []
|
||||
|
||||
|
||||
def _get_style_preset_suggestions(self) -> list[Suggestion]:
|
||||
"""Get style preset suggestions (without quotes)."""
|
||||
suggestions = []
|
||||
@@ -337,7 +337,7 @@ class FormattingCompletionEngine(BaseCompletionEngine):
|
||||
|
||||
try:
|
||||
# Use table_name from scope, or empty string as fallback
|
||||
table_name = scope.table_name or ""
|
||||
table_name = scope.table_name or self.table_name or ""
|
||||
values = self.provider.list_column_values(table_name, scope.column_name)
|
||||
suggestions = []
|
||||
for value in values:
|
||||
|
||||
@@ -3,228 +3,226 @@ from typing import Any, Callable
|
||||
from myfasthtml.core.formatting.condition_evaluator import ConditionEvaluator
|
||||
from myfasthtml.core.formatting.dataclasses import FormatRule
|
||||
from myfasthtml.core.formatting.formatter_resolver import FormatterResolver
|
||||
from myfasthtml.core.formatting.style_resolver import StyleResolver
|
||||
from myfasthtml.core.formatting.style_resolver import StyleResolver, StyleContainer
|
||||
|
||||
|
||||
class FormattingEngine:
|
||||
"""
|
||||
Main facade for the formatting system.
|
||||
|
||||
Combines:
|
||||
- ConditionEvaluator: evaluates conditions
|
||||
- StyleResolver: resolves styles to CSS
|
||||
- FormatterResolver: formats values for display
|
||||
- Conflict resolution: handles multiple matching rules
|
||||
|
||||
Usage:
|
||||
engine = FormattingEngine()
|
||||
rules = [
|
||||
FormatRule(style=Style(preset="error"), condition=Condition(operator="<", value=0)),
|
||||
FormatRule(formatter=NumberFormatter(preset="EUR")),
|
||||
]
|
||||
css, formatted = engine.apply_format(rules, cell_value=-5.0)
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
style_presets: dict = None,
|
||||
formatter_presets: dict = None,
|
||||
lookup_resolver: Callable[[str, str, str], dict] = None
|
||||
):
|
||||
"""
|
||||
Main facade for the formatting system.
|
||||
Initialize the FormattingEngine.
|
||||
|
||||
Combines:
|
||||
- ConditionEvaluator: evaluates conditions
|
||||
- StyleResolver: resolves styles to CSS
|
||||
- FormatterResolver: formats values for display
|
||||
- Conflict resolution: handles multiple matching rules
|
||||
|
||||
Usage:
|
||||
engine = FormattingEngine()
|
||||
rules = [
|
||||
FormatRule(style=Style(preset="error"), condition=Condition(operator="<", value=0)),
|
||||
FormatRule(formatter=NumberFormatter(preset="EUR")),
|
||||
]
|
||||
css, formatted = engine.apply_format(rules, cell_value=-5.0)
|
||||
Args:
|
||||
style_presets: Custom style presets. If None, uses defaults.
|
||||
formatter_presets: Custom formatter presets. If None, uses defaults.
|
||||
lookup_resolver: Function for resolving enum datagrid sources.
|
||||
"""
|
||||
self._condition_evaluator = ConditionEvaluator()
|
||||
self._style_resolver = StyleResolver(style_presets)
|
||||
self._formatter_resolver = FormatterResolver(formatter_presets, lookup_resolver)
|
||||
|
||||
def apply_format(
|
||||
self,
|
||||
rules: list[FormatRule],
|
||||
cell_value: Any,
|
||||
row_data: dict = None
|
||||
) -> tuple[StyleContainer | None, str | None]:
|
||||
"""
|
||||
Apply format rules to a cell value.
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
style_presets: dict = None,
|
||||
formatter_presets: dict = None,
|
||||
lookup_resolver: Callable[[str, str, str], dict] = None
|
||||
):
|
||||
"""
|
||||
Initialize the FormattingEngine.
|
||||
Args:
|
||||
rules: List of FormatRule to evaluate
|
||||
cell_value: The cell value to format
|
||||
row_data: Dict of {col_id: value} for column references
|
||||
|
||||
Args:
|
||||
style_presets: Custom style presets. If None, uses defaults.
|
||||
formatter_presets: Custom formatter presets. If None, uses defaults.
|
||||
lookup_resolver: Function for resolving enum datagrid sources.
|
||||
"""
|
||||
self._condition_evaluator = ConditionEvaluator()
|
||||
self._style_resolver = StyleResolver(style_presets)
|
||||
self._formatter_resolver = FormatterResolver(formatter_presets, lookup_resolver)
|
||||
Returns:
|
||||
Tuple of (css_string, formatted_value):
|
||||
- css_string: CSS inline style string, or None if no style
|
||||
- formatted_value: Formatted string, or None if no formatter
|
||||
"""
|
||||
if not rules:
|
||||
return None, None
|
||||
|
||||
# Find all matching rules
|
||||
matching_rules = self._get_matching_rules(rules, cell_value, row_data)
|
||||
|
||||
if not matching_rules:
|
||||
return None, None
|
||||
|
||||
# Resolve style and formatter independently
|
||||
# This allows combining style from one rule and formatter from another
|
||||
winning_style = self._resolve_style(matching_rules)
|
||||
winning_formatter = self._resolve_formatter(matching_rules)
|
||||
|
||||
# Apply style
|
||||
style = None
|
||||
if winning_style:
|
||||
style = self._style_resolver.to_style_container(winning_style)
|
||||
|
||||
# Apply formatter
|
||||
formatted_value = None
|
||||
if winning_formatter:
|
||||
formatted_value = self._formatter_resolver.resolve(winning_formatter, cell_value)
|
||||
|
||||
return style, formatted_value
|
||||
|
||||
def _get_matching_rules(
|
||||
self,
|
||||
rules: list[FormatRule],
|
||||
cell_value: Any,
|
||||
row_data: dict = None
|
||||
) -> list[FormatRule]:
|
||||
"""
|
||||
Get all rules that match the current cell.
|
||||
|
||||
def apply_format(
|
||||
self,
|
||||
rules: list[FormatRule],
|
||||
cell_value: Any,
|
||||
row_data: dict = None
|
||||
) -> tuple[str | None, str | None]:
|
||||
"""
|
||||
Apply format rules to a cell value.
|
||||
A rule matches if:
|
||||
- It has no condition (unconditional)
|
||||
- Its condition evaluates to True
|
||||
"""
|
||||
matching = []
|
||||
|
||||
for rule in rules:
|
||||
if rule.condition is None:
|
||||
# Unconditional rule always matches
|
||||
matching.append(rule)
|
||||
elif self._condition_evaluator.evaluate(rule.condition, cell_value, row_data):
|
||||
# Conditional rule matches
|
||||
matching.append(rule)
|
||||
|
||||
return matching
|
||||
|
||||
def _resolve_style(self, matching_rules: list[FormatRule]):
|
||||
"""
|
||||
Resolve style conflicts when multiple rules match.
|
||||
|
||||
Args:
|
||||
rules: List of FormatRule to evaluate
|
||||
cell_value: The cell value to format
|
||||
row_data: Dict of {col_id: value} for column references
|
||||
Resolution logic:
|
||||
1. Filter to rules that have a style
|
||||
2. Specificity = 1 if rule has condition, 0 otherwise
|
||||
3. Higher specificity wins
|
||||
4. At equal specificity, last rule wins
|
||||
|
||||
Returns:
|
||||
Tuple of (css_string, formatted_value):
|
||||
- css_string: CSS inline style string, or None if no style
|
||||
- formatted_value: Formatted string, or None if no formatter
|
||||
"""
|
||||
if not rules:
|
||||
return None, None
|
||||
Args:
|
||||
matching_rules: List of rules that matched
|
||||
|
||||
# Find all matching rules
|
||||
matching_rules = self._get_matching_rules(rules, cell_value, row_data)
|
||||
Returns:
|
||||
The winning Style, or None if no rules have style
|
||||
"""
|
||||
# Filter to rules with style
|
||||
style_rules = [rule for rule in matching_rules if rule.style is not None]
|
||||
|
||||
if not style_rules:
|
||||
return None
|
||||
|
||||
if len(style_rules) == 1:
|
||||
return style_rules[0].style
|
||||
|
||||
# Calculate specificity for each rule
|
||||
def get_specificity(rule: FormatRule) -> int:
|
||||
return 1 if rule.condition is not None else 0
|
||||
|
||||
# Find the maximum specificity
|
||||
max_specificity = max(get_specificity(rule) for rule in style_rules)
|
||||
|
||||
# Filter to rules with max specificity
|
||||
top_rules = [rule for rule in style_rules if get_specificity(rule) == max_specificity]
|
||||
|
||||
# Last rule wins among equal specificity
|
||||
return top_rules[-1].style
|
||||
|
||||
def _resolve_formatter(self, matching_rules: list[FormatRule]):
|
||||
"""
|
||||
Resolve formatter conflicts when multiple rules match.
|
||||
|
||||
if not matching_rules:
|
||||
return None, None
|
||||
Resolution logic:
|
||||
1. Filter to rules that have a formatter
|
||||
2. Specificity = 1 if rule has condition, 0 otherwise
|
||||
3. Higher specificity wins
|
||||
4. At equal specificity, last rule wins
|
||||
|
||||
# Resolve style and formatter independently
|
||||
# This allows combining style from one rule and formatter from another
|
||||
winning_style = self._resolve_style(matching_rules)
|
||||
winning_formatter = self._resolve_formatter(matching_rules)
|
||||
Args:
|
||||
matching_rules: List of rules that matched
|
||||
|
||||
# Apply style
|
||||
css_string = None
|
||||
if winning_style:
|
||||
css_string = self._style_resolver.to_css_string(winning_style)
|
||||
if css_string == "":
|
||||
css_string = None
|
||||
Returns:
|
||||
The winning Formatter, or None if no rules have formatter
|
||||
"""
|
||||
# Filter to rules with formatter
|
||||
formatter_rules = [rule for rule in matching_rules if rule.formatter is not None]
|
||||
|
||||
if not formatter_rules:
|
||||
return None
|
||||
|
||||
if len(formatter_rules) == 1:
|
||||
return formatter_rules[0].formatter
|
||||
|
||||
# Calculate specificity for each rule
|
||||
def get_specificity(rule: FormatRule) -> int:
|
||||
return 1 if rule.condition is not None else 0
|
||||
|
||||
# Find the maximum specificity
|
||||
max_specificity = max(get_specificity(rule) for rule in formatter_rules)
|
||||
|
||||
# Filter to rules with max specificity
|
||||
top_rules = [rule for rule in formatter_rules if get_specificity(rule) == max_specificity]
|
||||
|
||||
# Last rule wins among equal specificity
|
||||
return top_rules[-1].formatter
|
||||
|
||||
def _resolve_conflicts(self, matching_rules: list[FormatRule]) -> FormatRule | None:
|
||||
"""
|
||||
Resolve conflicts when multiple rules match.
|
||||
|
||||
# Apply formatter
|
||||
formatted_value = None
|
||||
if winning_formatter:
|
||||
formatted_value = self._formatter_resolver.resolve(winning_formatter, cell_value)
|
||||
DEPRECATED: This method is kept for backward compatibility but is no longer used.
|
||||
Use _resolve_style() and _resolve_formatter() instead.
|
||||
|
||||
return css_string, formatted_value
|
||||
Resolution logic:
|
||||
1. Specificity = 1 if rule has condition, 0 otherwise
|
||||
2. Higher specificity wins
|
||||
3. At equal specificity, last rule wins entirely (no fusion)
|
||||
|
||||
def _get_matching_rules(
|
||||
self,
|
||||
rules: list[FormatRule],
|
||||
cell_value: Any,
|
||||
row_data: dict = None
|
||||
) -> list[FormatRule]:
|
||||
"""
|
||||
Get all rules that match the current cell.
|
||||
Args:
|
||||
matching_rules: List of rules that matched
|
||||
|
||||
A rule matches if:
|
||||
- It has no condition (unconditional)
|
||||
- Its condition evaluates to True
|
||||
"""
|
||||
matching = []
|
||||
|
||||
for rule in rules:
|
||||
if rule.condition is None:
|
||||
# Unconditional rule always matches
|
||||
matching.append(rule)
|
||||
elif self._condition_evaluator.evaluate(rule.condition, cell_value, row_data):
|
||||
# Conditional rule matches
|
||||
matching.append(rule)
|
||||
|
||||
return matching
|
||||
|
||||
def _resolve_style(self, matching_rules: list[FormatRule]):
|
||||
"""
|
||||
Resolve style conflicts when multiple rules match.
|
||||
|
||||
Resolution logic:
|
||||
1. Filter to rules that have a style
|
||||
2. Specificity = 1 if rule has condition, 0 otherwise
|
||||
3. Higher specificity wins
|
||||
4. At equal specificity, last rule wins
|
||||
|
||||
Args:
|
||||
matching_rules: List of rules that matched
|
||||
|
||||
Returns:
|
||||
The winning Style, or None if no rules have style
|
||||
"""
|
||||
# Filter to rules with style
|
||||
style_rules = [rule for rule in matching_rules if rule.style is not None]
|
||||
|
||||
if not style_rules:
|
||||
return None
|
||||
|
||||
if len(style_rules) == 1:
|
||||
return style_rules[0].style
|
||||
|
||||
# Calculate specificity for each rule
|
||||
def get_specificity(rule: FormatRule) -> int:
|
||||
return 1 if rule.condition is not None else 0
|
||||
|
||||
# Find the maximum specificity
|
||||
max_specificity = max(get_specificity(rule) for rule in style_rules)
|
||||
|
||||
# Filter to rules with max specificity
|
||||
top_rules = [rule for rule in style_rules if get_specificity(rule) == max_specificity]
|
||||
|
||||
# Last rule wins among equal specificity
|
||||
return top_rules[-1].style
|
||||
|
||||
def _resolve_formatter(self, matching_rules: list[FormatRule]):
|
||||
"""
|
||||
Resolve formatter conflicts when multiple rules match.
|
||||
|
||||
Resolution logic:
|
||||
1. Filter to rules that have a formatter
|
||||
2. Specificity = 1 if rule has condition, 0 otherwise
|
||||
3. Higher specificity wins
|
||||
4. At equal specificity, last rule wins
|
||||
|
||||
Args:
|
||||
matching_rules: List of rules that matched
|
||||
|
||||
Returns:
|
||||
The winning Formatter, or None if no rules have formatter
|
||||
"""
|
||||
# Filter to rules with formatter
|
||||
formatter_rules = [rule for rule in matching_rules if rule.formatter is not None]
|
||||
|
||||
if not formatter_rules:
|
||||
return None
|
||||
|
||||
if len(formatter_rules) == 1:
|
||||
return formatter_rules[0].formatter
|
||||
|
||||
# Calculate specificity for each rule
|
||||
def get_specificity(rule: FormatRule) -> int:
|
||||
return 1 if rule.condition is not None else 0
|
||||
|
||||
# Find the maximum specificity
|
||||
max_specificity = max(get_specificity(rule) for rule in formatter_rules)
|
||||
|
||||
# Filter to rules with max specificity
|
||||
top_rules = [rule for rule in formatter_rules if get_specificity(rule) == max_specificity]
|
||||
|
||||
# Last rule wins among equal specificity
|
||||
return top_rules[-1].formatter
|
||||
|
||||
def _resolve_conflicts(self, matching_rules: list[FormatRule]) -> FormatRule | None:
|
||||
"""
|
||||
Resolve conflicts when multiple rules match.
|
||||
|
||||
DEPRECATED: This method is kept for backward compatibility but is no longer used.
|
||||
Use _resolve_style() and _resolve_formatter() instead.
|
||||
|
||||
Resolution logic:
|
||||
1. Specificity = 1 if rule has condition, 0 otherwise
|
||||
2. Higher specificity wins
|
||||
3. At equal specificity, last rule wins entirely (no fusion)
|
||||
|
||||
Args:
|
||||
matching_rules: List of rules that matched
|
||||
|
||||
Returns:
|
||||
The winning FormatRule, or None if no rules
|
||||
"""
|
||||
if not matching_rules:
|
||||
return None
|
||||
|
||||
if len(matching_rules) == 1:
|
||||
return matching_rules[0]
|
||||
|
||||
# Calculate specificity for each rule
|
||||
# Specificity = 1 if has condition, 0 otherwise
|
||||
def get_specificity(rule: FormatRule) -> int:
|
||||
return 1 if rule.condition is not None else 0
|
||||
|
||||
# Find the maximum specificity
|
||||
max_specificity = max(get_specificity(rule) for rule in matching_rules)
|
||||
|
||||
# Filter to rules with max specificity
|
||||
top_rules = [rule for rule in matching_rules if get_specificity(rule) == max_specificity]
|
||||
|
||||
# Last rule wins among equal specificity
|
||||
return top_rules[-1]
|
||||
Returns:
|
||||
The winning FormatRule, or None if no rules
|
||||
"""
|
||||
if not matching_rules:
|
||||
return None
|
||||
|
||||
if len(matching_rules) == 1:
|
||||
return matching_rules[0]
|
||||
|
||||
# Calculate specificity for each rule
|
||||
# Specificity = 1 if has condition, 0 otherwise
|
||||
def get_specificity(rule: FormatRule) -> int:
|
||||
return 1 if rule.condition is not None else 0
|
||||
|
||||
# Find the maximum specificity
|
||||
max_specificity = max(get_specificity(rule) for rule in matching_rules)
|
||||
|
||||
# Filter to rules with max specificity
|
||||
top_rules = [rule for rule in matching_rules if get_specificity(rule) == max_specificity]
|
||||
|
||||
# Last rule wins among equal specificity
|
||||
return top_rules[-1]
|
||||
|
||||
@@ -3,40 +3,31 @@
|
||||
|
||||
DEFAULT_STYLE_PRESETS = {
|
||||
"primary": {
|
||||
"background-color": "var(--color-primary)",
|
||||
"color": "var(--color-primary-content)",
|
||||
"__class__": "mf-formatting-primary",
|
||||
},
|
||||
"secondary": {
|
||||
"background-color": "var(--color-secondary)",
|
||||
"color": "var(--color-secondary-content)",
|
||||
"__class__": "mf-formatting-secondary",
|
||||
},
|
||||
"accent": {
|
||||
"background-color": "var(--color-accent)",
|
||||
"color": "var(--color-accent-content)",
|
||||
"__class__": "mf-formatting-accent",
|
||||
},
|
||||
"neutral": {
|
||||
"background-color": "var(--color-neutral)",
|
||||
"color": "var(--color-neutral-content)",
|
||||
"__class__": "mf-formatting-neutral",
|
||||
},
|
||||
"info": {
|
||||
"background-color": "var(--color-info)",
|
||||
"color": "var(--color-info-content)",
|
||||
"__class__": "mf-formatting-info",
|
||||
},
|
||||
"success": {
|
||||
"background-color": "var(--color-success)",
|
||||
"color": "var(--color-success-content)",
|
||||
"__class__": "mf-formatting-success",
|
||||
},
|
||||
"warning": {
|
||||
"background-color": "var(--color-warning)",
|
||||
"color": "var(--color-warning-content)",
|
||||
"__class__": "mf-formatting-warning",
|
||||
},
|
||||
"error": {
|
||||
"background-color": "var(--color-error)",
|
||||
"color": "var(--color-error-content)",
|
||||
"__class__": "mf-formatting-error",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# === Formatter Presets ===
|
||||
|
||||
DEFAULT_FORMATTER_PRESETS = {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from myfasthtml.core.formatting.dataclasses import Style
|
||||
from myfasthtml.core.formatting.presets import DEFAULT_STYLE_PRESETS
|
||||
|
||||
|
||||
# Mapping from Python attribute names to CSS property names
|
||||
PROPERTY_NAME_MAP = {
|
||||
"background_color": "background-color",
|
||||
@@ -13,63 +14,90 @@ PROPERTY_NAME_MAP = {
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class StyleContainer:
|
||||
cls: str | None = None
|
||||
css: str = None
|
||||
|
||||
|
||||
class StyleResolver:
|
||||
"""Resolves styles by applying presets and explicit properties."""
|
||||
"""Resolves styles by applying presets and explicit properties."""
|
||||
|
||||
def __init__(self, style_presets: dict = None):
|
||||
"""
|
||||
Initialize the StyleResolver.
|
||||
|
||||
def __init__(self, style_presets: dict = None):
|
||||
"""
|
||||
Initialize the StyleResolver.
|
||||
Args:
|
||||
style_presets: Custom style presets dict. If None, uses DEFAULT_STYLE_PRESETS.
|
||||
"""
|
||||
self.style_presets = style_presets or DEFAULT_STYLE_PRESETS
|
||||
|
||||
def resolve(self, style: Style) -> dict:
|
||||
"""
|
||||
Resolve a Style to CSS properties dict.
|
||||
|
||||
Args:
|
||||
style_presets: Custom style presets dict. If None, uses DEFAULT_STYLE_PRESETS.
|
||||
"""
|
||||
self.style_presets = style_presets or DEFAULT_STYLE_PRESETS
|
||||
Logic:
|
||||
1. If preset is defined, load preset properties
|
||||
2. Override with explicit properties (non-None values)
|
||||
3. Convert Python names to CSS names
|
||||
|
||||
def resolve(self, style: Style) -> dict:
|
||||
"""
|
||||
Resolve a Style to CSS properties dict.
|
||||
Args:
|
||||
style: The Style object to resolve
|
||||
|
||||
Logic:
|
||||
1. If preset is defined, load preset properties
|
||||
2. Override with explicit properties (non-None values)
|
||||
3. Convert Python names to CSS names
|
||||
Returns:
|
||||
Dict of CSS properties, e.g. {"background-color": "red", "color": "white"}
|
||||
"""
|
||||
if style is None:
|
||||
return {}
|
||||
|
||||
result = {}
|
||||
|
||||
# Apply preset first
|
||||
if style.preset and style.preset in self.style_presets:
|
||||
preset_props = self.style_presets[style.preset]
|
||||
for css_name, value in preset_props.items():
|
||||
result[css_name] = value
|
||||
|
||||
# Override with explicit properties
|
||||
for py_name, css_name in PROPERTY_NAME_MAP.items():
|
||||
value = getattr(style, py_name, None)
|
||||
if value is not None:
|
||||
result[css_name] = value
|
||||
|
||||
return result
|
||||
|
||||
def to_css_string(self, style: Style) -> str:
|
||||
"""
|
||||
Resolve a Style to a CSS inline string.
|
||||
|
||||
Args:
|
||||
style: The Style object to resolve
|
||||
Args:
|
||||
style: The Style object to resolve
|
||||
|
||||
Returns:
|
||||
Dict of CSS properties, e.g. {"background-color": "red", "color": "white"}
|
||||
"""
|
||||
if style is None:
|
||||
return {}
|
||||
Returns:
|
||||
CSS string, e.g. "background-color: red; color: white;"
|
||||
"""
|
||||
props = self.resolve(style)
|
||||
if not props:
|
||||
return ""
|
||||
|
||||
props.pop("__class__", "")
|
||||
return "; ".join(f"{key}: {value}" for key, value in props.items()) + ";"
|
||||
|
||||
def to_style_container(self, style: Style) -> StyleContainer:
|
||||
"""
|
||||
Resolve a Style to a class that contains the class name and the CSS inline string.
|
||||
|
||||
result = {}
|
||||
Args:
|
||||
style: The Style object to resolve
|
||||
|
||||
# Apply preset first
|
||||
if style.preset and style.preset in self.style_presets:
|
||||
preset_props = self.style_presets[style.preset]
|
||||
for css_name, value in preset_props.items():
|
||||
result[css_name] = value
|
||||
|
||||
# Override with explicit properties
|
||||
for py_name, css_name in PROPERTY_NAME_MAP.items():
|
||||
value = getattr(style, py_name, None)
|
||||
if value is not None:
|
||||
result[css_name] = value
|
||||
|
||||
return result
|
||||
|
||||
def to_css_string(self, style: Style) -> str:
|
||||
"""
|
||||
Resolve a Style to a CSS inline string.
|
||||
|
||||
Args:
|
||||
style: The Style object to resolve
|
||||
|
||||
Returns:
|
||||
CSS string, e.g. "background-color: red; color: white;"
|
||||
"""
|
||||
props = self.resolve(style)
|
||||
if not props:
|
||||
return ""
|
||||
return "; ".join(f"{key}: {value}" for key, value in props.items()) + ";"
|
||||
Returns:
|
||||
CSS string, e.g. "background-color: red; color: white;" and the class name
|
||||
"""
|
||||
props = self.resolve(style)
|
||||
if not props:
|
||||
return StyleContainer(None, "")
|
||||
|
||||
cls = props.pop("__class__", None)
|
||||
css = "; ".join(f"{key}: {value}" for key, value in props.items()) + ";"
|
||||
|
||||
return StyleContainer(cls, css)
|
||||
|
||||
Reference in New Issue
Block a user