SX docs: configurable shell, SX-native event handlers, nav fixes

- Configurable page shell (~sx-page-shell kwargs + SX_SHELL app config)
  so each app controls its own assets — sx docs loads only sx-browser.js
- SX-evaluated sx-on:* handlers (eval-expr instead of new Function)
  with DOM primitives registered in PRIMITIVES table
- data-init boot mode for pure SX initialization scripts
- Jiggle animation on links while fetching
- Nav: 3-column grid for centered alignment, is-leaf sizing,
  fix map-indexed param order (index, item), guard mod-by-zero
- Async route eval failure now falls back to server fetch
  instead of silently rendering nothing
- Remove duplicate h1 title from ~doc-page
- Re-bootstrap sx-ref.js + sx-browser.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 11:00:59 +00:00
parent 31a6e708fc
commit 8a5c115557
20 changed files with 5763 additions and 3046 deletions

View File

@@ -67,6 +67,34 @@ def create_app() -> "Quart":
**extra_kw,
)
# Minimal shell — no Prism, no SweetAlert, no body.js
# sx docs uses custom highlight.py, not Prism; body.js is for legacy apps
app.config["SX_SHELL"] = {
"head_scripts": [], # no CDN scripts
"body_scripts": [], # no body.js
"inline_head_js": "", # no pre-boot JS (hover-capable, close-details unused)
"inline_css": (
".sx-indicator{display:none}"
".sx-request .sx-indicator{display:inline-flex}"
"@keyframes sxJiggle{0%,100%{transform:translateX(0)}"
"25%{transform:translateX(-.5px)}75%{transform:translateX(.5px)}}"
"a.sx-request{animation:sxJiggle .3s ease-in-out infinite}"
),
# Nav link aria-selected update on client-side routing — pure SX
"init_sx": (
'(dom-listen (dom-body) "sx:clientRoute"'
' (fn (e)'
' (let ((p (get (event-detail e) "pathname")))'
' (when p'
' (for-each'
' (fn (a) (dom-set-attr a "aria-selected" "false"))'
' (dom-query-all "nav a[aria-selected]"))'
' (for-each'
' (fn (a) (dom-set-attr a "aria-selected" "true"))'
' (dom-query-all (str "nav a[href=\\"" p "\\"]")))))))'
),
}
from sxc.pages import setup_sx_pages
setup_sx_pages()

View File

@@ -2,7 +2,7 @@
(defcomp ~sx-home-content ()
(div :id "main-content" :class "max-w-3xl mx-auto px-4 py-6"
(highlight "(defcomp ~sx-header ()
(~doc-code :code (highlight "(defcomp ~sx-header ()
(a :href \"/\"
:sx-get \"/\" :sx-target \"#main-panel\"
:sx-select \"#main-panel\"
@@ -13,7 +13,7 @@
(p :class \"text-lg text-stone-500 mb-1\"
\"Framework free reactive hypermedia\")
(p :class \"text-xs text-stone-400\"
\"© Giles Bradshaw 2026\")))" "lisp")))
\"© Giles Bradshaw 2026\")))" "lisp"))))
(defcomp ~docs-introduction-content ()
(~doc-page :title "Introduction"

View File

@@ -21,35 +21,41 @@
(p :class "text-xs text-stone-400"
"© Giles Bradshaw 2026")))
;; Current section with annotated prev/next siblings.
;; Desktop: prev ← Current → next (horizontal)
;; Mobile: stacked vertically
(defcomp ~nav-sibling-row (&key node siblings)
(let* ((idx (find-nav-index siblings node))
(count (len siblings))
(prev-idx (mod (+ (- idx 1) count) count))
(next-idx (mod (+ idx 1) count))
(prev-node (nth siblings prev-idx))
(next-node (nth siblings next-idx)))
(div :class "max-w-3xl mx-auto px-4 py-2 flex items-center justify-center gap-4"
(a :href (get prev-node "href")
:sx-get (get prev-node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-sm text-stone-500 hover:text-violet-600"
(str "← " (get prev-node "label")))
(a :href (get node "href")
:sx-get (get node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-lg font-semibold text-violet-700 px-4"
(get node "label"))
(a :href (get next-node "href")
:sx-get (get next-node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-sm text-stone-500 hover:text-violet-600"
(str (get next-node "label") " →")))))
;; @css grid grid-cols-3
;; Current section with prev/next siblings.
;; 3-column grid: prev is right-aligned, current centered, next left-aligned.
;; Current page is larger in the leaf (bottom) row.
(defcomp ~nav-sibling-row (&key node siblings is-leaf)
(let* ((sibs (or siblings (list)))
(count (len sibs)))
(when (> count 0)
(let* ((idx (find-nav-index sibs node))
(prev-idx (mod (+ (- idx 1) count) count))
(next-idx (mod (+ idx 1) count))
(prev-node (nth sibs prev-idx))
(next-node (nth sibs next-idx)))
(div :class "max-w-3xl mx-auto px-4 py-2 grid grid-cols-3 items-center"
(a :href (get prev-node "href")
:sx-get (get prev-node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-sm text-stone-500 hover:text-violet-600 text-right"
(str "← " (get prev-node "label")))
(a :href (get node "href")
:sx-get (get node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class (if is-leaf
"text-2xl font-bold text-violet-700 text-center px-4"
"text-lg font-semibold text-violet-700 text-center px-4")
(get node "label"))
(a :href (get next-node "href")
:sx-get (get next-node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-sm text-stone-500 hover:text-violet-600 text-left"
(str (get next-node "label") " →")))))))
;; Children links — shown as clearly clickable buttons.
(defcomp ~nav-children (&key items)
@@ -70,16 +76,20 @@
;; ---------------------------------------------------------------------------
(defcomp ~sx-doc (&key path &rest children) :affinity :server
(let ((nav-state (resolve-nav-path sx-nav-tree (or path "/"))))
(let* ((nav-state (resolve-nav-path sx-nav-tree (or path "/")))
(trail (or (get nav-state "trail") (list)))
(trail-len (len trail)))
(<>
(div :id "sx-nav" :class "mb-6"
(~sx-header)
;; Sibling arrows for EVERY level in the trail
(map (fn (crumb)
;; Last row (leaf) gets is-leaf for larger current page title
(map-indexed (fn (i crumb)
(~nav-sibling-row
:node (get crumb "node")
:siblings (get crumb "siblings")))
(get nav-state "trail"))
:siblings (get crumb "siblings")
:is-leaf (= i (- trail-len 1))))
trail)
;; Children as button links
(when (get nav-state "children")
(~nav-children :items (get nav-state "children"))))

View File

@@ -2,7 +2,6 @@
(defcomp ~doc-page (&key title &rest children)
(div :class "max-w-4xl mx-auto px-6 py-8"
(h1 :class "text-4xl font-bold text-stone-900 mb-8 text-center" title)
(div :class "prose prose-stone max-w-none space-y-6" children)))
(defcomp ~doc-section (&key title id &rest children)