Commitfcb436c2Recorded19 Feb 2026Repositorysigil-css

feat: Add css-sel for compound selectors, eliminate css-raw from demo

Message

Add css-sel function to join multiple selectors with ", " for shared properties. Update css macro to correctly apply & suffixes to each part of compound selectors via %css-sel-append. Replace all 7 css-raw calls in the demo with proper DSL: css-sel for selector lists, & for pseudo- elements, and unquoted attribute selectors.

Changed
 src/sigil/css.sgl | 33 +++++++++++++++++++++++++++++++--
 test/test-css.sgl | 40 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 71 insertions(+), 2 deletions(-)
Diff
src/sigil/css.sglmodified
@@ -70,6 +70,9 @@
70
css-import ; (css-import url) - @import for fonts, etc.
71
css-raw ; (css-raw "...") - raw CSS string
72
+73
;; Selector helpers
+74
css-sel ; (css-sel ".a" ".b") - join selectors with ", "
+75
76
;; Combinators
77
css-combine ; combine multiple css fragments
78
@@ -96,6 +99,7 @@
99
%value->string
100
%values->string
101
%css-keyframe-step
+102
%css-sel-append
103
)
104
105
(begin
@@ -177,6 +181,31 @@
181
(string-join (map %value->string vals) " "))
182
183
+184
;; ========== Selector Helpers ==========
+185
+186
;;; Build a compound CSS selector from multiple selectors.
+187
;;;
+188
;;; Joins selectors with ", " for rules that share the same properties.
+189
;;;
+190
;;; ```scheme
+191
;;; (css (css-sel ".btn" "button[type=submit]")
+192
;;; (padding "0.5rem 1rem")
+193
;;; (background "#007bff"))
+194
;;; ```
+195
(define (css-sel . selectors)
+196
(string-join selectors ", "))
+197
+198
;; Append a suffix to each selector in a (possibly compound) selector.
+199
;; Handles both simple selectors and comma-separated lists correctly.
+200
;; % prefix = exported for macro hygiene
+201
(define (%css-sel-append base suffix)
+202
(let ((parts (string-split base ", ")))
+203
(if (null? (cdr parts))
+204
(string-append base suffix)
+205
(string-join (map (lambda (s) (string-append s suffix)) parts)
+206
", "))))
+207
+208
209
;; ========== Rule Constructors ==========
210
211
;;; Main CSS rule constructor (macro version)
@@ -196,14 +225,14 @@
225
;; Nested rule with & suffix
226
((css selector (& suffix item ...) rest ...)
227
(let ((parent (css selector rest ...))
199
(nested (css (string-append selector suffix) item ...)))
+228
(nested (css (%css-sel-append selector suffix) item ...)))
229
(%make-rule (%rule-selector parent)
230
(%rule-props parent)
231
(append (%rule-children parent) (list nested)))))
232
;; Nested css rule (child selector)
233
((css selector (css child-sel item ...) rest ...)
234
(let ((parent (css selector rest ...))
206
(nested (css (string-append selector " " child-sel) item ...)))
+235
(nested (css (%css-sel-append selector (string-append " " child-sel)) item ...)))
236
(%make-rule (%rule-selector parent)
237
(%rule-props parent)
238
(append (%rule-children parent) (list nested)))))
test/test-css.sglmodified
@@ -173,6 +173,46 @@
173
(assert-true (string-contains? result ".button:hover {"))
174
(assert-true (string-contains? result "@media (max-width: 600px)")))))
175
+176
(test-group "css-sel"
+177
(test "joins selectors with comma"
+178
(assert-equal (css-sel ".a" ".b") ".a, .b"))
+179
+180
(test "single selector passes through"
+181
(assert-equal (css-sel ".a") ".a"))
+182
+183
(test "three selectors"
+184
(assert-equal (css-sel ".a" ".b" ".c") ".a, .b, .c"))
+185
+186
(test "compound selector in css rule"
+187
(let ((result (css->string
+188
(css (css-sel ".btn" "button[type=submit]")
+189
(padding "0.5rem")))))
+190
(assert-true (string-contains? result ".btn, button[type=submit] {"))))
+191
+192
(test "compound selector with & nesting"
+193
(let ((result (css->string
+194
(css (css-sel ".btn" "button[type=submit]")
+195
(color "white")
+196
(& ":hover"
+197
(background "blue"))))))
+198
(assert-true (string-contains? result ".btn, button[type=submit] {"))
+199
(assert-true (string-contains? result ".btn:hover, button[type=submit]:hover {"))))
+200
+201
(test "pseudo-element with &"
+202
(let ((result (css->string
+203
(css "dialog"
+204
(border "none")
+205
(& "::backdrop"
+206
(background "rgba(0,0,0,0.5)"))))))
+207
(assert-true (string-contains? result "dialog {"))
+208
(assert-true (string-contains? result "dialog::backdrop {"))))
+209
+210
(test "attribute selector without quotes"
+211
(let ((result (css->string
+212
(css "input[type=text]"
+213
(padding "0.5rem")))))
+214
(assert-true (string-contains? result "input[type=text] {")))))
+215
216
;;; Helper for string-contains? if not available
217
(define (string-contains? str substr)
218
(let ((str-len (string-length str))