deven broke tailwind
This commit is contained in:
@@ -1304,6 +1304,7 @@ func twCompile(input, baseDir string, candidates []string) (string, int, error)
|
|||||||
var keyframes []*AstNode
|
var keyframes []*AstNode
|
||||||
var passthrough []*AstNode
|
var passthrough []*AstNode
|
||||||
var customUtilities []*AstNode
|
var customUtilities []*AstNode
|
||||||
|
var customVariants []*AstNode
|
||||||
var safelistAdd []string
|
var safelistAdd []string
|
||||||
var safelistRemove []string
|
var safelistRemove []string
|
||||||
hasPreflight := false
|
hasPreflight := false
|
||||||
@@ -1361,6 +1362,8 @@ func twCompile(input, baseDir string, candidates []string) (string, int, error)
|
|||||||
processTheme(node)
|
processTheme(node)
|
||||||
case node.Kind == nAtRule && node.Name == "@utility":
|
case node.Kind == nAtRule && node.Name == "@utility":
|
||||||
customUtilities = append(customUtilities, node)
|
customUtilities = append(customUtilities, node)
|
||||||
|
case node.Kind == nAtRule && node.Name == "@custom-variant":
|
||||||
|
customVariants = append(customVariants, node)
|
||||||
case node.Kind == nAtRule && node.Name == "@source":
|
case node.Kind == nAtRule && node.Name == "@source":
|
||||||
literals, negate := parseSourceDirective(node.Params)
|
literals, negate := parseSourceDirective(node.Params)
|
||||||
if negate {
|
if negate {
|
||||||
@@ -1399,6 +1402,20 @@ func twCompile(input, baseDir string, candidates []string) (string, int, error)
|
|||||||
|
|
||||||
ds := buildDesignSystem(theme)
|
ds := buildDesignSystem(theme)
|
||||||
|
|
||||||
|
// Register @custom-variant blocks. This is how a project defines `dark:` as a CLASS
|
||||||
|
// toggle rather than a media query — the built-in dark variant follows the OS, which
|
||||||
|
// a site with a theme switch cannot use:
|
||||||
|
//
|
||||||
|
// @custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
//
|
||||||
|
// Both of Tailwind's forms are accepted: the shorthand above, and the block form
|
||||||
|
// with an explicit @slot.
|
||||||
|
for _, cv := range customVariants {
|
||||||
|
if name, body, ok := parseCustomVariant(cv); ok {
|
||||||
|
ds.variants.fromAst(name, body, ds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Register @utility blocks as static utilities.
|
// Register @utility blocks as static utilities.
|
||||||
for _, u := range customUtilities {
|
for _, u := range customUtilities {
|
||||||
name := strings.TrimSpace(u.Params)
|
name := strings.TrimSpace(u.Params)
|
||||||
@@ -8813,6 +8830,95 @@ func (v *Variants) kind(name string) variantKind {
|
|||||||
|
|
||||||
func (v *Variants) keys() []string { return v.order }
|
func (v *Variants) keys() []string { return v.order }
|
||||||
|
|
||||||
|
// parseCustomVariant reads an @custom-variant at-rule into a name and the AST body that
|
||||||
|
// fromAst expects (a body whose rules contain an @slot where the utility goes).
|
||||||
|
//
|
||||||
|
// Two forms, both from Tailwind:
|
||||||
|
//
|
||||||
|
// @custom-variant dark (&:where(.dark, .dark *)); // shorthand
|
||||||
|
//
|
||||||
|
// @custom-variant dark { // block, explicit slot
|
||||||
|
// &:where(.dark, .dark *) { @slot; }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// In the shorthand, a parenthesised selector starting with '@' is an at-rule
|
||||||
|
// (`@custom-variant any-hover (@media (any-hover: hover))`), and anything else is a
|
||||||
|
// selector. Several may be given, comma-separated at the top level.
|
||||||
|
func parseCustomVariant(node *AstNode) (name string, body []*AstNode, ok bool) {
|
||||||
|
params := strings.TrimSpace(node.Params)
|
||||||
|
if params == "" {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// The name is the first token; whatever follows is the shorthand's parenthesised part.
|
||||||
|
i := strings.IndexAny(params, " \t(")
|
||||||
|
if i < 0 {
|
||||||
|
// No shorthand: it must be the block form, which carries its own @slot.
|
||||||
|
if len(node.Nodes) == 0 {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
return params, node.Nodes, true
|
||||||
|
}
|
||||||
|
name = strings.TrimSpace(params[:i])
|
||||||
|
rest := strings.TrimSpace(params[i:])
|
||||||
|
|
||||||
|
if rest == "" {
|
||||||
|
if len(node.Nodes) == 0 {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
return name, node.Nodes, true
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(rest, "(") || !strings.HasSuffix(rest, ")") {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
inner := strings.TrimSpace(rest[1 : len(rest)-1])
|
||||||
|
if inner == "" {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sel := range splitTopLevel(inner, ',') {
|
||||||
|
sel = strings.TrimSpace(sel)
|
||||||
|
if sel == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
slot := atRule("@slot", "")
|
||||||
|
if strings.HasPrefix(sel, "@") {
|
||||||
|
// "@media (any-hover: hover)" -> name "@media", params "(any-hover: hover)"
|
||||||
|
at, params, _ := strings.Cut(sel, " ")
|
||||||
|
body = append(body, atRule(at, strings.TrimSpace(params), slot))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
body = append(body, styleRule(sel, slot))
|
||||||
|
}
|
||||||
|
if len(body) == 0 {
|
||||||
|
return "", nil, false
|
||||||
|
}
|
||||||
|
return name, body, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// splitTopLevel splits on sep, ignoring separators nested inside brackets — a selector
|
||||||
|
// list like `&:where(.dark, .dark *)` is ONE selector, and splitting it on its inner
|
||||||
|
// comma would produce two broken halves.
|
||||||
|
func splitTopLevel(s string, sep byte) []string {
|
||||||
|
var parts []string
|
||||||
|
depth := 0
|
||||||
|
start := 0
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch s[i] {
|
||||||
|
case '(', '[':
|
||||||
|
depth++
|
||||||
|
case ')', ']':
|
||||||
|
depth--
|
||||||
|
case sep:
|
||||||
|
if depth == 0 {
|
||||||
|
parts = append(parts, s[start:i])
|
||||||
|
start = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return append(parts, s[start:])
|
||||||
|
}
|
||||||
|
|
||||||
func (v *Variants) compoundsWith(parent string, child *Variant) bool {
|
func (v *Variants) compoundsWith(parent string, child *Variant) bool {
|
||||||
parentInfo, ok := v.variants[parent]
|
parentInfo, ok := v.variants[parent]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user