Fix duplicate sx-cssx-live style tags
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 58s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 58s
Cache the style element reference in _cssx-style-el so flush-cssx-to-dom never creates more than one. Previous code called dom-query on every flush, which could miss the element during rapid successive calls, creating duplicates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -299,9 +299,7 @@
|
||||
(parsed-ok (signal false))
|
||||
(error-msg (signal nil)))
|
||||
(letrec
|
||||
((container-ref nil)
|
||||
(dom-stack (list))
|
||||
(built-nodes (list))
|
||||
((dom-stack (list))
|
||||
(split-tag (fn (expr result)
|
||||
(cond
|
||||
(not (list? expr))
|
||||
@@ -320,31 +318,24 @@
|
||||
(cond
|
||||
(= (type-of a) "keyword") (do (set! ckw true) (append! cat a))
|
||||
ckw (do (set! ckw false) (append! cat a))
|
||||
;; Component children (~cssx/tw etc) → collect as spreads for open step
|
||||
(and (list? a) (not (empty? a))
|
||||
(= (type-of (first a)) "symbol")
|
||||
(starts-with? (symbol-name (first a)) "~"))
|
||||
(do (set! ckw false) (append! spreads a))
|
||||
:else (do (set! ckw false) (append! cch a))))
|
||||
cargs)
|
||||
;; Open step includes spreads — they're evaluated to set attrs on the element
|
||||
(append! result {"type" "open" "tag" ctag "attrs" cat "spreads" spreads})
|
||||
(for-each (fn (c) (split-tag c result)) cch)
|
||||
(append! result {"type" "close" "tag" ctag}))
|
||||
:else
|
||||
(append! result {"type" "expr" "expr" expr}))))
|
||||
(do-parse (fn ()
|
||||
(console-log "do-parse called")
|
||||
(reset! error-msg nil)
|
||||
(reset! step-idx 0)
|
||||
(reset! parsed-ok false)
|
||||
(set! dom-stack (list))
|
||||
(set! built-nodes (list))
|
||||
;; Clear preview container
|
||||
(let ((c (dom-query "#render-preview")))
|
||||
(console-log "container found:" c)
|
||||
(when c (set! container-ref c)
|
||||
(dom-set-prop c "innerHTML" "")))
|
||||
(let ((container (dom-query "#render-preview")))
|
||||
(when container (dom-set-prop container "innerHTML" "")))
|
||||
(let ((parsed (sx-parse (deref source))))
|
||||
(if (empty? parsed)
|
||||
(do (reset! error-msg "Parse error") (reset! steps (list)))
|
||||
@@ -352,55 +343,45 @@
|
||||
(split-tag (first parsed) result)
|
||||
(reset! steps result)
|
||||
(reset! parsed-ok true)
|
||||
;; Set up DOM stack with the preview container as root
|
||||
(set! dom-stack (list (dom-query "#render-preview"))))))))
|
||||
(do-step (fn ()
|
||||
(console-log "do-step: idx=" (deref step-idx) "len=" (len (deref steps)) "stack=" (len dom-stack) "parent=" (if (empty? dom-stack) "nil" "ok"))
|
||||
(when (and (deref parsed-ok) (< (deref step-idx) (len (deref steps))))
|
||||
(let ((step (nth (deref steps) (deref step-idx)))
|
||||
(step-type (get step "type"))
|
||||
(parent (if (empty? dom-stack) (dom-query "#render-preview") (last dom-stack))))
|
||||
(console-log " step-type=" step-type "parent=" parent)
|
||||
(cond
|
||||
(= step-type "open")
|
||||
(let ((el (dom-create-element (get step "tag") nil))
|
||||
(attrs (get step "attrs"))
|
||||
(spreads (or (get step "spreads") (list))))
|
||||
;; Set keyword attrs
|
||||
(let loop ((i 0))
|
||||
(when (< i (len attrs))
|
||||
(dom-set-attr el (keyword-name (nth attrs i)) (nth attrs (+ i 1)))
|
||||
(loop (+ i 2))))
|
||||
;; Evaluate spread components (e.g. ~cssx/tw) and apply to element
|
||||
(for-each (fn (sp)
|
||||
(let ((rendered (render-to-dom sp (make-env) nil)))
|
||||
(when (and rendered (spread? rendered))
|
||||
(let ((sattrs (spread-attrs rendered)))
|
||||
(let ((result (eval-expr sp (make-env))))
|
||||
(when (and result (spread? result))
|
||||
(let ((sattrs (spread-attrs result)))
|
||||
(for-each (fn (k)
|
||||
(if (= k "class")
|
||||
(dom-set-attr el "class"
|
||||
(str (or (dom-get-attr el "class") "") " " (get sattrs k)))
|
||||
(dom-set-attr el k (get sattrs k))))
|
||||
(keys sattrs))))
|
||||
nil))
|
||||
(keys sattrs))))))
|
||||
spreads)
|
||||
(when parent (dom-append parent el))
|
||||
(set! dom-stack (append dom-stack (list el)))
|
||||
(set! built-nodes (append built-nodes (list el))))
|
||||
(set! dom-stack (append dom-stack (list el))))
|
||||
(= step-type "close")
|
||||
(when (> (len dom-stack) 1)
|
||||
(set! dom-stack (slice dom-stack 0 (- (len dom-stack) 1))))
|
||||
(= step-type "leaf")
|
||||
(let ((val (get step "expr")))
|
||||
(console-log " leaf:" val "parent=" parent)
|
||||
(when parent
|
||||
(when parent
|
||||
(let ((val (get step "expr")))
|
||||
(dom-append parent (create-text-node (if (string? val) val (str val))))))
|
||||
(= step-type "expr")
|
||||
(let ((rendered (render-to-dom (get step "expr") (make-env) nil)))
|
||||
(console-log " expr rendered:" rendered)
|
||||
(when (and parent rendered)
|
||||
(dom-append parent rendered)
|
||||
nil))))
|
||||
(dom-append parent rendered)))))
|
||||
(swap! step-idx inc))))
|
||||
(do-run (fn ()
|
||||
(let loop ()
|
||||
@@ -409,13 +390,15 @@
|
||||
(loop)))))
|
||||
(do-back (fn ()
|
||||
(when (and (deref parsed-ok) (> (deref step-idx) 0))
|
||||
;; Reset and replay up to step-idx - 1
|
||||
(let ((target (- (deref step-idx) 1)))
|
||||
(do-parse)
|
||||
(let loop ((n 0))
|
||||
(when (< n target)
|
||||
(let ((target (- (deref step-idx) 1))
|
||||
(container (dom-query "#render-preview")))
|
||||
(when container (dom-set-prop container "innerHTML" ""))
|
||||
(set! dom-stack (list (dom-query "#render-preview")))
|
||||
(reset! step-idx 0)
|
||||
(let loop ()
|
||||
(when (< (deref step-idx) target)
|
||||
(do-step)
|
||||
(loop (+ n 1)))))))))
|
||||
(loop))))))))
|
||||
(div :class "space-y-4"
|
||||
(div
|
||||
(label :class "text-xs text-stone-400 block mb-1" "Component expression")
|
||||
@@ -482,6 +465,7 @@
|
||||
|
||||
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Demo page content
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; SX app boot — styles and behaviors injected on page load
|
||||
;; SX app boot — styles, behaviors, and post-render hooks
|
||||
;;
|
||||
;; Replaces inline_css and init_sx from Python app config.
|
||||
;; Called as a data-init script on every page.
|
||||
@@ -11,6 +11,25 @@
|
||||
(collect! "cssx" "@keyframes sxJiggle{0%,100%{transform:translateX(0)}25%{transform:translateX(-.5px)}75%{transform:translateX(.5px)}}")
|
||||
(collect! "cssx" "a.sx-request{animation:sxJiggle .3s ease-in-out infinite}")
|
||||
|
||||
;; CSSX flush hook — inject collected CSS rules into a <style> tag.
|
||||
;; The spec calls (run-post-render-hooks) after hydration/swap/mount.
|
||||
;; This is the application's CSS injection strategy.
|
||||
(console-log "init-client: registering cssx flush hook, type:" (type-of (fn () nil)))
|
||||
(register-post-render-hook
|
||||
(fn ()
|
||||
(console-log "cssx flush: running, rules:" (len (collected "cssx")))
|
||||
(let ((rules (collected "cssx")))
|
||||
(when (not (empty? rules))
|
||||
(let ((style (or (dom-query "[data-cssx]")
|
||||
(let ((s (dom-create-element "style" nil)))
|
||||
(dom-set-attr s "data-cssx" "")
|
||||
(dom-append-to-head s)
|
||||
s))))
|
||||
(dom-set-prop style "textContent"
|
||||
(str (or (dom-get-prop style "textContent") "")
|
||||
(join "" rules))))
|
||||
(clear-collected! "cssx")))))
|
||||
|
||||
;; Nav link aria-selected update on client-side routing
|
||||
(dom-listen (dom-body) "sx:clientRoute"
|
||||
(fn (e)
|
||||
|
||||
Reference in New Issue
Block a user