mirror of
https://github.com/Textualize/textual.git
synced 2025-10-17 02:38:12 +03:00
Merge branch 'css' into scroll-view
This commit is contained in:
@@ -285,7 +285,7 @@ class Stylesheet:
|
|||||||
_check_rule = self._check_rule
|
_check_rule = self._check_rule
|
||||||
|
|
||||||
# Collect the rules defined in the stylesheet
|
# Collect the rules defined in the stylesheet
|
||||||
for rule in self.rules:
|
for rule in reversed(self.rules):
|
||||||
for specificity in _check_rule(rule, node):
|
for specificity in _check_rule(rule, node):
|
||||||
for key, rule_specificity, value in rule.styles.extract_rules(
|
for key, rule_specificity, value in rule.styles.extract_rules(
|
||||||
specificity
|
specificity
|
||||||
@@ -301,6 +301,7 @@ class Stylesheet:
|
|||||||
for name, specificity_rules in rule_attributes.items()
|
for name, specificity_rules in rule_attributes.items()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
self.replace_rules(node, node_rules, animate=animate)
|
self.replace_rules(node, node_rules, animate=animate)
|
||||||
|
|
||||||
node.component_styles.clear()
|
node.component_styles.clear()
|
||||||
|
|||||||
@@ -1,23 +1,42 @@
|
|||||||
from ._datatable import DataTable
|
from __future__ import annotations
|
||||||
from ._footer import Footer
|
from importlib import import_module
|
||||||
from ._header import Header
|
import typing
|
||||||
from ._button import Button
|
|
||||||
from ._placeholder import Placeholder
|
|
||||||
from ._static import Static
|
|
||||||
from ._tree_control import TreeControl, TreeClick, TreeNode, NodeID
|
|
||||||
from ._directory_tree import DirectoryTree, FileClick
|
|
||||||
|
|
||||||
|
from ..case import camel_to_snake
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from ..widget import Widget
|
||||||
|
|
||||||
|
|
||||||
|
# ⚠️For any new built-in Widget we create, not only we have to add them to the following list, but also to the
|
||||||
|
# `__init__.pyi` file in this same folder - otherwise text editors and type checkers won't be able to "see" them.
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Button",
|
"Button",
|
||||||
"DataTable",
|
"DataTable",
|
||||||
"DirectoryTree",
|
"DirectoryTree",
|
||||||
"FileClick",
|
|
||||||
"Footer",
|
"Footer",
|
||||||
"Header",
|
"Header",
|
||||||
"Placeholder",
|
"Placeholder",
|
||||||
"Static",
|
"Static",
|
||||||
"TreeClick",
|
|
||||||
"TreeControl",
|
"TreeControl",
|
||||||
"TreeNode",
|
|
||||||
"NodeID",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
_WIDGETS_LAZY_LOADING_CACHE: dict[str, type[Widget]] = {}
|
||||||
|
|
||||||
|
# Let's decrease startup time by lazy loading our Widgets:
|
||||||
|
def __getattr__(widget_class: str) -> type[Widget]:
|
||||||
|
try:
|
||||||
|
return _WIDGETS_LAZY_LOADING_CACHE[widget_class]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if widget_class not in __all__:
|
||||||
|
raise ImportError(f"Package 'textual.widgets' has no class '{widget_class}'")
|
||||||
|
|
||||||
|
widget_module_path = f"._{camel_to_snake(widget_class)}"
|
||||||
|
module = import_module(widget_module_path, package="textual.widgets")
|
||||||
|
class_ = getattr(module, widget_class)
|
||||||
|
|
||||||
|
_WIDGETS_LAZY_LOADING_CACHE[widget_class] = class_
|
||||||
|
return class_
|
||||||
|
|||||||
8
src/textual/widgets/__init__.pyi
Normal file
8
src/textual/widgets/__init__.pyi
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# This stub file must re-export every classes exposed in the __init__.py's `__all__` list:
|
||||||
|
from ._button import Button as Button
|
||||||
|
from ._directory_tree import DirectoryTree as DirectoryTree
|
||||||
|
from ._footer import Footer as Footer
|
||||||
|
from ._header import Header as Header
|
||||||
|
from ._placeholder import Placeholder as Placeholder
|
||||||
|
from ._static import Static as Static
|
||||||
|
from ._tree_control import TreeControl as TreeControl
|
||||||
@@ -7,6 +7,98 @@ from textual.color import Color
|
|||||||
from textual.css._help_renderables import HelpText
|
from textual.css._help_renderables import HelpText
|
||||||
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
from textual.css.stylesheet import Stylesheet, StylesheetParseError
|
||||||
from textual.css.tokenizer import TokenizeError
|
from textual.css.tokenizer import TokenizeError
|
||||||
|
from textual.dom import DOMNode
|
||||||
|
from textual.geometry import Spacing
|
||||||
|
|
||||||
|
|
||||||
|
def _make_stylesheet(css: str) -> Stylesheet:
|
||||||
|
stylesheet = Stylesheet()
|
||||||
|
stylesheet.source["test.css"] = css
|
||||||
|
stylesheet.parse()
|
||||||
|
return stylesheet
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_apply_highest_specificity_wins():
|
||||||
|
"""#ids have higher specificity than .classes"""
|
||||||
|
css = "#id {color: red;} .class {color: blue;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(classes="class", id="id")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.color == Color(255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_apply_doesnt_override_defaults():
|
||||||
|
css = "#id {color: red;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(id="id")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.margin == Spacing.all(0)
|
||||||
|
assert node.styles.box_sizing == "border-box"
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_apply_highest_specificity_wins_multiple_classes():
|
||||||
|
"""When we use two selectors containing only classes, then the selector
|
||||||
|
`.b.c` has greater specificity than the selector `.a`"""
|
||||||
|
css = ".b.c {background: blue;} .a {background: red; color: lime;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(classes="a b c")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.background == Color(0, 0, 255)
|
||||||
|
assert node.styles.color == Color(0, 255, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_many_classes_dont_overrule_id():
|
||||||
|
"""#id is further to the left in the specificity tuple than class, and
|
||||||
|
a selector containing multiple classes cannot take priority over even a
|
||||||
|
single class."""
|
||||||
|
css = "#id {color: red;} .a.b.c.d {color: blue;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(classes="a b c d", id="id")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.color == Color(255, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_last_rule_wins_when_same_rule_twice_in_one_ruleset():
|
||||||
|
css = "#id {color: red; color: blue;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(id="id")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.color == Color(0, 0, 255)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_rulesets_merged_for_duplicate_selectors():
|
||||||
|
css = "#id {color: red; background: lime;} #id {color:blue;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(id="id")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.color == Color(0, 0, 255)
|
||||||
|
assert node.styles.background == Color(0, 255, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_apply_takes_final_rule_in_specificity_clash():
|
||||||
|
""".a and .b both contain background and have same specificity, so .b wins
|
||||||
|
since it was declared last - the background should be blue."""
|
||||||
|
css = ".a {background: red; color: lime;} .b {background: blue;}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(classes="a b", id="c")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
assert node.styles.color == Color(0, 255, 0) # color: lime
|
||||||
|
assert node.styles.background == Color(0, 0, 255) # background: blue
|
||||||
|
|
||||||
|
|
||||||
|
def test_stylesheet_apply_empty_rulesets():
|
||||||
|
"""Ensure that we don't crash when working with empty rulesets"""
|
||||||
|
css = ".a {} .b {}"
|
||||||
|
stylesheet = _make_stylesheet(css)
|
||||||
|
node = DOMNode(classes="a b")
|
||||||
|
stylesheet.apply(node)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
|||||||
Reference in New Issue
Block a user