Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CSS Inheritance with Nested Pseudo-Selectors #5206

Open
ecoue opened this issue Nov 4, 2024 · 7 comments
Open

CSS Inheritance with Nested Pseudo-Selectors #5206

ecoue opened this issue Nov 4, 2024 · 7 comments

Comments

@ecoue
Copy link

ecoue commented Nov 4, 2024

Report

When extending Textual's Input widget with custom CSS using nested selectors, child classes do not properly inherit all CSS rules, specifically those within pseudo-selectors and modifier classes (:hover is working). The issue manifests when using the nested & syntax for pseudo-selectors and modifiers. However, the same CSS rules work correctly when written with non-nested syntax.

Reproduction

from textual.widgets import Input as BaseInput

class Input(BaseInput):
    DEFAULT_CSS = """
    Input {
        color: $text;
        background: $primary-lighten-1;
        border: none;
        width: 1fr;
        height: auto;
        padding: 1 2;

        &:focus {
            border: none;
        }

        &:hover {
            background: $primary-lighten-2;
        }

        &.-invalid {
            background: $error-lighten-1;
            border: none;

            &:focus {
                border: none;
            }

            &:hover {
                background: $error-lighten-2;
            }
        }
    }
    """

class InlineInput(Input):
    DEFAULT_CSS = """
    InlineInput {
        padding: 0 1;
    }
    """

Expectation

  • The InlineInput widget should inherit all CSS rules from its parent class
  • When focused, the InlineInput should have no border, as specified in the parent's &:focus rule

Behavior

  • The InlineInput widget shows a border when focused
  • Other CSS rules from the parent class appear to be inherited correctly (i.e. &:hover)
  • The same CSS rules work as expected when written with non-nested syntax (using Input:focus instead of &:focus)
class Input(BaseInput):
    DEFAULT_CSS = """
    Input {
        color: $text;
        background: $primary-lighten-1;
        border: none;
        width: 1fr;
        height: auto;
        padding: 1 2;
    }

    Input:focus {
        border: none;
    }

    Input:hover {
        background: $primary-lighten-2;
    }

    Input.-invalid {
        background: $error-lighten-1;
        border: none;
    }

    Input.-invalid:focus {
        border: none;
    }

    Input.-invalid:hover {
        background: $error-lighten-2;
    }
    """

Diagnostics

Versions

Name Value
Textual 0.73.0
Rich 13.7.1

Python

Name Value
Version 3.12.4
Implementation CPython
Compiler Clang 15.0.0 (clang-1500.3.9.4)
Executable /Users/.../env/bin/python

Operating System

Name Value
System Darwin
Release 23.6.0
Version Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:21 PDT 2024; root:xnu-10063.141.2~1/RELEASE_ARM64_T8103

Terminal

Name Value
Terminal Application iTerm.app (3.5.5)
TERM xterm-256color
COLORTERM truecolor
FORCE_COLOR Not set
NO_COLOR Not set

Rich Console options

Name Value
size width=132, height=41
legacy_windows False
min_width 1
max_width 132
is_terminal True
encoding utf-8
max_height 41
justify None
overflow None
no_wrap False
highlight None
markup None
height None
@ecoue ecoue changed the title CSS Inheritance Bug with Nested Selectors in Custom Input Widgets CSS Inheritance with Nested Pseudo-Selectors Nov 4, 2024
@Textualize Textualize deleted a comment from github-actions bot Nov 4, 2024
@willmcgugan
Copy link
Collaborator

The type in the CSS is the name of the class when it was defined. By styling Input you are clashing with the builtin Input, and the usual specificity rules apply.

To create a custom widget, you will need to create a new name (don't call it Input again) and style that.

class MyInput(Input):
   DEFAULT_CSS = """
   MyWidget {
      ...
   }
   """

"""

This will ensure that the rules you add in MyInput take precedence over the base class Input.

If you need further help, please include a fully working MRE as requested...

If you can, include a complete working example that demonstrates the bug. Check it can run without modifications.

@ecoue
Copy link
Author

ecoue commented Nov 4, 2024

Thank you very much for the quick reply. I had already suspected that the problem could be a naming conflict. Unfortunately, the problem exists independently of this. Rather, it is noticeable that there are no problems (even when named as Input) if no nested CSS is used. This should not produce a different result in any case since it is just syntactic sugar. Furthermore, the problem only exists with the additional subclass InlineInput; Input, on the other hand, works.

@willmcgugan
Copy link
Collaborator

I will need a fully working example to assist you further.

@ecoue
Copy link
Author

ecoue commented Nov 4, 2024

from textual.app import App
from textual.widgets import Input


class MyInputNested(Input):
    DEFAULT_CSS = """
    MyInputNested {
        border: none;
        padding: 1 2;
        margin-bottom: 1;

        &:focus {
            border: none;
        }
    }
    """


class MyInputNestedSubclass(MyInputNested):
    DEFAULT_CSS = """
    MyInputNestedSubclass {
        padding: 0 2;
        height: auto;
    }
    """


class MyInput(Input):
    DEFAULT_CSS = """
    MyInput {
        border: none;
        padding: 1 2;
        margin-bottom: 1;
    }

    MyInput:focus {
        border: none;
    }
    """


class MyInputSubclass(MyInput):
    DEFAULT_CSS = """
    MyInputSubclass {
        padding: 0 2;
        height: auto;
    }
    """


class BugDemo(App):
    def compose(self):
        yield MyInputNested(placeholder="MyInputNested")
        yield MyInputNestedSubclass(placeholder="MyInputNestedSubclass")

        yield MyInput(placeholder="MyInput")
        yield MyInputSubclass(placeholder="MyInputSubclass")


if __name__ == "__main__":
    app = BugDemo()
    app.run()

@ecoue
Copy link
Author

ecoue commented Nov 4, 2024

Oddly the nested version only stops working if you add an instance of its subclass to the app.

@TomJGooding
Copy link
Contributor

You're running quite an old version of Textual, have you tried upgrading? Because I can't seem to reproduce this with the latest version.

@ecoue
Copy link
Author

ecoue commented Nov 5, 2024

You are absolutely right, should have checked that first. The bug seems to be fixed with the current version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants