diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index 8f4d7a5..20c9fd3 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -368,6 +368,8 @@ :children (list {:label "Reference" :href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items} {:label "Examples" :href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items})} + {:label "Spreads" :href "/sx/(geography.(spreads))" + :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, and the path to provide/context/emit!."} {:label "Marshes" :href "/sx/(geography.(marshes))" :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."} {:label "Isomorphism" :href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items})} diff --git a/sx/sx/spreads.sx b/sx/sx/spreads.sx new file mode 100644 index 0000000..0558f3b --- /dev/null +++ b/sx/sx/spreads.sx @@ -0,0 +1,235 @@ +;; --------------------------------------------------------------------------- +;; Spreads — child-to-parent communication across render boundaries +;; --------------------------------------------------------------------------- + +(defcomp ~geography/spreads-content () + (~docs/page :title "Spreads" + + (p :class "text-stone-500 text-sm italic mb-8" + "A spread is a value that, when returned as a child of an element, " + "injects attributes onto its parent instead of rendering as content. " + "This inverts the normal direction of data flow: children tell parents how to look.") + + ;; ===================================================================== + ;; I. The primitives + ;; ===================================================================== + + (~docs/section :title "Three primitives" :id "primitives" + (p "The spread system has three orthogonal primitives. Each operates at a " + "different level of the render pipeline.") + + (~docs/subsection :title "1. make-spread / spread? / spread-attrs" + (p "A spread is a value type. " (code "make-spread") " creates one from a dict of " + "attributes. When the renderer encounters a spread as a child of an element, " + "it merges the attrs onto the parent element instead of appending a DOM node.") + (~docs/code :code "(defcomp ~highlight (&key colour) + (make-spread {\"class\" (str \"highlight-\" colour) + \"data-highlight\" colour}))") + (p "Use it as a child of any element:") + (~docs/code :code "(div (~highlight :colour \"yellow\") + \"This div gets class=highlight-yellow\")") + (p (code "class") " values are appended (space-joined). " + (code "style") " values are appended (semicolon-joined). " + "All other attributes overwrite.")) + + (~docs/subsection :title "2. collect! / collected / clear-collected!" + (p "Render-time accumulators. Values are collected into named buckets " + "during rendering and retrieved at flush points. Deduplication is automatic.") + (~docs/code :code ";; Deep inside a component tree: +(collect! \"cssx\" \".sx-bg-red-500{background-color:hsl(0,72%,53%)}\") + +;; At the flush point (once, in the layout): +(let ((rules (collected \"cssx\"))) + (clear-collected! \"cssx\") + (raw! (str \"\")))") + (p "This is upward communication through the render tree: " + "a deeply nested component contributes a CSS rule, and the layout " + "emits all accumulated rules as a single " (code "