Refactor spread to use provide/emit! internally

Spreads now emit their attrs into the nearest element's provide scope
instead of requiring per-child spread? checks at every intermediate
layer. emit! is tolerant (no-op when no provider), so spreads in
non-element contexts silently vanish.

- adapter-html: element/lake/marsh wrap children in provide, collect
  emitted; removed 14 spread filters from fragment, forms, components
- adapter-sx: aser wraps result to catch spread values from fn calls;
  aser-call uses provide with attr-parts/child-parts ordering
- adapter-async: same pattern for both render and aser paths
- adapter-dom: added emit! in spread dispatch + provide in element
  rendering; kept spread? checks for reactive/island and DOM safety
- platform: emit! returns NIL when no provider instead of erroring
- 3 new aser tests: stored spread, nested element, silent drop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 15:41:32 +00:00
parent 859aad4333
commit 2de4ba8c57
9 changed files with 444 additions and 526 deletions

View File

@@ -44,8 +44,8 @@
;; Pre-rendered DOM node → pass through
"dom-node" expr
;; Spread → pass through (parent element handles it)
"spread" expr
;; Spread → emit attrs to nearest element provider, pass through for reactive-spread
"spread" (do (emit! "element-attrs" (spread-attrs expr)) expr)
;; Dict → empty
"dict" (create-fragment)
@@ -180,6 +180,9 @@
:else ns))
(el (dom-create-element tag new-ns)))
;; Provide scope for spread emit! — deeply nested spreads emit here
(provide-push! "element-attrs" nil)
;; Process args: keywords → attrs, others → children
(reduce
(fn (state arg)
@@ -236,28 +239,8 @@
;; Reactive spread: track signal deps, update attrs on change
(and (spread? child) *island-scope*)
(reactive-spread el (fn () (render-to-dom arg env new-ns)))
;; Static spread: one-shot merge attrs onto parent element
(spread? child)
(for-each
(fn ((key :as string))
(let ((val (dict-get (spread-attrs child) key)))
(if (= key "class")
;; Class: append to existing
(let ((existing (dom-get-attr el "class")))
(dom-set-attr el "class"
(if (and existing (not (= existing "")))
(str existing " " val)
val)))
(if (= key "style")
;; Style: append with semicolon
(let ((existing (dom-get-attr el "style")))
(dom-set-attr el "style"
(if (and existing (not (= existing "")))
(str existing ";" val)
val)))
;; Other attrs: overwrite
(dom-set-attr el key (str val))))))
(keys (spread-attrs child)))
;; Static spread: already emitted via provide, skip
(spread? child) nil
;; Normal child: append to element
:else
(dom-append el child))))
@@ -265,6 +248,29 @@
(dict "i" 0 "skip" false)
args)
;; Collect emitted spread attrs and merge onto DOM element
(for-each
(fn (spread-dict)
(for-each
(fn ((key :as string))
(let ((val (dict-get spread-dict key)))
(if (= key "class")
(let ((existing (dom-get-attr el "class")))
(dom-set-attr el "class"
(if (and existing (not (= existing "")))
(str existing " " val)
val)))
(if (= key "style")
(let ((existing (dom-get-attr el "style")))
(dom-set-attr el "style"
(if (and existing (not (= existing "")))
(str existing ";" val)
val)))
(dom-set-attr el key (str val))))))
(keys spread-dict)))
(emitted "element-attrs"))
(provide-pop! "element-attrs")
el)))