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: Support for selectors of pseudo-classes, pseudo-elements. And nested css #262

Open
otaxhu opened this issue Oct 30, 2023 · 14 comments
Open

Comments

@otaxhu
Copy link

otaxhu commented Oct 30, 2023

I was experimenting with the library and found that there is no way to access the :hover pseudo-class nor any other pseudo-class or pseudo-element.

First I tried nested css, something like this:

package main

css myCss() {
    background-color: red;
    &:hover {
        background-color: green;
    }
}

Error message after trying generation of the code above:

failed to process path: /path/to/template.templ parsing error: css property expression: missing closing brace: line 4, col 4

I know that the css expression is like a class in a css file so I think that it could be the only one way to do it. Other way to do it could be with SCSS support or some other crazy thing

@otaxhu otaxhu changed the title Support for selectors of pseudo-classes, pseudo-elements. And nested css (css expression): Support for selectors of pseudo-classes, pseudo-elements. And nested css Oct 30, 2023
@willoma
Copy link

willoma commented Dec 31, 2023

No need for SCSS: regular CSS supports nesting, I think templ may simply put it in the result without any transformation...

@otaxhu
Copy link
Author

otaxhu commented Jan 4, 2024

That is not 100% accurate if your target browser is an older one.

@willoma
Copy link

willoma commented Jan 5, 2024

Sure!
I did not say that all browsers support CSS nesting, but that nesting is now supported by regular CSS, therefore I think templ should allow us to do CSS nesting :-)

However, I think including SCSS into templ for nesting would be overkill...

@willoma
Copy link

willoma commented Jan 5, 2024

After thinking again about this, I agree it does not fix the pseudo-class problem with non-nested CSS...

@joerdav joerdav changed the title (css expression): Support for selectors of pseudo-classes, pseudo-elements. And nested css syntax: Support for selectors of pseudo-classes, pseudo-elements. And nested css Jan 30, 2024
@joerdav joerdav added enhancement New feature or request parser NeedsDecision Issue needs some more discussion so a decision can be made labels Jan 30, 2024
@joerdav joerdav changed the title syntax: Support for selectors of pseudo-classes, pseudo-elements. And nested css parser: Support for selectors of pseudo-classes, pseudo-elements. And nested css Jan 30, 2024
@joerdav
Copy link
Collaborator

joerdav commented Jan 30, 2024

I believe this one still requires some decisions to be made, so have labelled as such so noone jumps to an implementation.

@joerdav joerdav changed the title parser: Support for selectors of pseudo-classes, pseudo-elements. And nested css css: Support for selectors of pseudo-classes, pseudo-elements. And nested css Jan 30, 2024
@a-h
Copy link
Owner

a-h commented Feb 17, 2024

I'm thinking that we may be able to implement CSS variables directly in <style> tags and provide an alternative to the existing style components that provides everything we might need.

The tdewolff parser is suitably open to extension such that I was able to put together a rough PoC of how JS variables could be introduced directly into script tags, without needing to have script components.

package main

import (
	"fmt"
	"io"
	"log"
	"strings"
	"unicode"

	"github.com/a-h/templ/parser/v2/goexpression"
	"github.com/tdewolff/parse/v2"
	"github.com/tdewolff/parse/v2/js"
)

func NewJSExpressionParser(content string) *JSExpressionParser {
	input := parse.NewInputString(content)
	return &JSExpressionParser{
		Content:       content,
		Input:         input,
		Lexer:         js.NewLexer(input),
		BraceCount:    0,
		GoExpressions: nil,
	}
}

type JSExpressionParser struct {
	Content string
	Input   *parse.Input
	Lexer   *js.Lexer
	// BraceCount is the number of sequential braces we've just seen.
	// If we see `{{` then we should start parsing Go code.
	BraceCount    int
	GoExpressions []Range
}

type Range struct {
	Index   int
	Content string
}

func (p *JSExpressionParser) Parse() (err error) {
	for {
		tt, _ := p.Lexer.Next()
		switch tt {
		case js.ErrorToken:
			if p.Lexer.Err() != io.EOF {
				return p.Lexer.Err()
			}
			return
		case js.OpenBraceToken:
			p.BraceCount++
			if p.BraceCount == 2 {
				expr := Range{
					Index: p.Input.Offset(),
				}
				_, e, err := goexpression.Expression(p.Content[p.Input.Offset():])
				if err != nil {
					return fmt.Errorf("failed to parse Go: %w", err)
				}
				expr.Content = p.Content[expr.Index : expr.Index+e]
				p.GoExpressions = append(p.GoExpressions, expr)

				// Seek past the end of the Go expression we just parsed.
				p.Input.Move(e + 1)

				// Get rid of whitespace.
				for {
					r, l := p.Input.PeekRune(1)
					if unicode.IsSpace(r) {
						p.Input.Move(l)
						continue
					}
					break
				}

				// Clear braces.
				if err = take(p.Input, "}}"); err != nil {
					return fmt.Errorf("unclosed Go expression: %v", err)
				}
			}
		default:
			p.BraceCount = 0
		}
	}
}

func take(input *parse.Input, expected string) error {
	var actual strings.Builder
	for i := 0; i < len(expected); i++ {
		a, _ := input.PeekRune(i)
		actual.WriteRune(a)
	}
	if expected != actual.String() {
		return fmt.Errorf("expected %q, got %q", expected, actual.String())
	}
	input.Move(len(expected))
	return nil
}

func main() {
	content := `const x = {{ y }};`
	p := NewJSExpressionParser(content)
	err := p.Parse()
	if err != nil {
		log.Fatalf("failed to parse: %v", err)
	}
	fmt.Printf("%#+v", p.GoExpressions)
}

I think the same logic could be applied to CSS components, allowing the use of this sort of thing:

templ Page(bgColor string) {
  <style type="text/css">
  @media print {
    body {
      background-color: {{ bgColor }};
    }
  }
  </style>
}

Some work would be required to think about if/how class names get scoped to their components, but the parser part, at least, looks like it's not hard to achieve.

@zapling
Copy link

zapling commented Feb 18, 2024

Some work would be required to think about if/how class names get scoped to their components, but the parser part, at least, looks like it's not hard to achieve.

On the topic of how class name would be scoped, Vue does this by looking for a special scoped property on the style tag.

<style scoped>
.myClass { }
</style>

https://vuejs.org/api/sfc-css-features.html#scoped-css

Another use cases that might be worth considering is when a project have seperate css files. It would be nice to be able to use embed on those files and also then get access to the class name scoping feature.

@viirak
Copy link
Contributor

viirak commented Apr 7, 2024

what's the solution for this now?

@zapling
Copy link

zapling commented Apr 7, 2024

what's the solution for this now?

I used a plain CSS file and added conditional logic to add specific class names.

<head>
  <link rel="stylesheet" href="/assets/style.css"/>
</head>

@hecs
Copy link

hecs commented Apr 25, 2024

First if all. Thank you for you work with templ. I love everything about it! (except for googling the name "templ") :)

I think CSS nesting and pseudo selectors would be fantastic to have in templ. And even other CSS features like media queries.

Here's my thoughts:
When using templ + HTMX. Templ is giving a very nice way of "Componentify" each view.
CSS scoping is usually handled by a big javascript framework + bundler + css preprocessor (like angular). In templ its just there out of the box. Right now it is limited by a number if CSS-features it doesn't support. It feels like its almost there.
I have huge respect for if this is difficult to implement. But it would be truly awesome to have, I think.

@jimafisk
Copy link

we may be able to implement CSS variables directly in <style> tags and provide an alternative to the existing style components that provides everything we might need

This would be much more ergonomic than declaring style components!

Some work would be required to think about if/how class names get scoped to their components

I was searching the issue queue because I wanted to open a feature request for this! I always liked how Svelte automatically scopes elements / classes / ids to the markup of the current component. They also allow a :global() modifier if you want to break out to the global scope, which is really handy. I wasn't sure if the project maintainers wanted scoped styles to be default behavior (I'm not against it), so I was going to suggest an optional scoped attribute exactly like @zapling mentioned: #262 (comment)

Another use cases that might be worth considering is when a project have seperate css files. It would be nice to be able to use embed on those files and also then get access to the class name scoping feature.

I was hoping for a way to export these scoped style declarations from each component and collect them into an external stylesheet instead of having <style> tags sprinkled throughout the dom. Maybe that's possible already, but just need more examples to understand: #517 (comment)

Just thought I'd add my input as a data point, huge thanks to the maintainers for such a great project!

@ainsleyclark
Copy link

Is there anything in the pipeline that's being worked on? Nested CSS would be a game changer.

@a-h
Copy link
Owner

a-h commented May 17, 2024

I've documented my current thinking in the issue above. ^

@ainsleyclark
Copy link

Would it be possible to have an "Unsafe" flag to pass to templ for the time being so all CSS is rendered within the block? (whilst this being worked on)

@linear linear bot added Migrated and removed NeedsDecision Issue needs some more discussion so a decision can be made enhancement New feature or request Migrated parser labels Sep 4, 2024
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

9 participants