Implement 7 missing sx attributes: boost, preload, preserve, indicator, validate, ignore, optimistic
Add sx-preserve/sx-ignore (morph skip), sx-indicator (loading element), sx-validate (form validation), sx-boost (progressive enhancement), sx-preload (hover prefetch with 30s cache), and sx-optimistic (instant UI preview with rollback). Move all from HTMX_MISSING_ATTRS to SX_UNIQUE_ATTRS with full ATTR_DETAILS docs and reference.sx demos. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -406,3 +406,148 @@
|
||||
(div :data-sx "(div :class \"p-3 bg-emerald-50 rounded\" (h3 :class \"font-semibold text-emerald-800\" title) (p :class \"text-sm text-stone-600\" message))"
|
||||
:data-sx-env "{\"title\": \"Dynamic content\", \"message\": \"Variables passed via data-sx-env are available in the expression.\"}")
|
||||
(p :class "text-xs text-stone-400" "The title and message above come from the data-sx-env JSON.")))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-boost
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-boost-demo ()
|
||||
(div :class "space-y-3"
|
||||
(nav :sx-boost "true" :class "flex gap-3"
|
||||
(a :href "/reference/attributes/sx-get"
|
||||
:class "text-violet-600 hover:text-violet-800 underline text-sm"
|
||||
"sx-get")
|
||||
(a :href "/reference/attributes/sx-post"
|
||||
:class "text-violet-600 hover:text-violet-800 underline text-sm"
|
||||
"sx-post")
|
||||
(a :href "/reference/attributes/sx-target"
|
||||
:class "text-violet-600 hover:text-violet-800 underline text-sm"
|
||||
"sx-target"))
|
||||
(p :class "text-xs text-stone-400"
|
||||
"These links use AJAX navigation via sx-boost — no sx-get needed on each link.")))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-preload
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-preload-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/reference/api/time"
|
||||
:sx-target "#ref-preload-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-preload "mouseover"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
|
||||
"Hover then click (preloaded)")
|
||||
(div :id "ref-preload-result"
|
||||
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
|
||||
"Hover over the button first, then click — the response is instant.")))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-preserve
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-preserve-demo ()
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2 items-center"
|
||||
(button
|
||||
:sx-get "/reference/api/time"
|
||||
:sx-target "#ref-preserve-container"
|
||||
:sx-swap "innerHTML"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
|
||||
"Swap container")
|
||||
(span :class "text-xs text-stone-400" "The input below keeps its value across swaps."))
|
||||
(div :id "ref-preserve-container" :class "space-y-2"
|
||||
(input :id "ref-preserved-input" :sx-preserve "true"
|
||||
:type "text" :placeholder "Type here — preserved across swaps"
|
||||
:class "w-full px-3 py-2 border border-stone-300 rounded text-sm")
|
||||
(div :class "p-2 bg-stone-50 rounded text-sm text-stone-600"
|
||||
"This text will be replaced on swap."))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-indicator
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-indicator-demo ()
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-3 items-center"
|
||||
(button
|
||||
:sx-get "/reference/api/slow-echo"
|
||||
:sx-target "#ref-indicator-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-indicator "#ref-spinner"
|
||||
:sx-vals "{\"q\": \"hello\"}"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
|
||||
"Load (slow)")
|
||||
(span :id "ref-spinner"
|
||||
:class "text-violet-600 text-sm"
|
||||
:style "display: none"
|
||||
"Loading..."))
|
||||
(div :id "ref-indicator-result"
|
||||
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
|
||||
"Click to load (indicator shows during request).")))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-validate
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-validate-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/reference/api/greet"
|
||||
:sx-target "#ref-validate-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-validate "true"
|
||||
:class "flex gap-2"
|
||||
(input :type "email" :name "name" :required "true"
|
||||
:placeholder "Enter email (required)"
|
||||
:class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
|
||||
(button :type "submit"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
|
||||
"Submit"))
|
||||
(div :id "ref-validate-result"
|
||||
:class "p-3 rounded bg-stone-50 text-stone-400 text-sm"
|
||||
"Submit with invalid/empty email to see validation.")))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-ignore
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-ignore-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/reference/api/time"
|
||||
:sx-target "#ref-ignore-container"
|
||||
:sx-swap "innerHTML"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
|
||||
"Swap container")
|
||||
(div :id "ref-ignore-container" :class "space-y-2"
|
||||
(div :sx-ignore "true" :class "p-2 bg-amber-50 rounded border border-amber-200"
|
||||
(p :class "text-sm text-amber-800" "This subtree has sx-ignore — it won't change.")
|
||||
(input :type "text" :placeholder "Type here — ignored during swap"
|
||||
:class "mt-1 w-full px-2 py-1 border border-amber-300 rounded text-sm"))
|
||||
(div :class "p-2 bg-stone-50 rounded text-sm text-stone-600"
|
||||
"This text WILL be replaced on swap."))))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; sx-optimistic
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defcomp ~ref-optimistic-demo ()
|
||||
(div :class "space-y-2"
|
||||
(div :id "ref-opt-item-1"
|
||||
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
|
||||
(span :class "text-sm text-stone-700" "Optimistic item A")
|
||||
(button :sx-delete "/reference/api/item/opt1"
|
||||
:sx-target "#ref-opt-item-1" :sx-swap "delete"
|
||||
:sx-optimistic "remove"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
|
||||
(div :id "ref-opt-item-2"
|
||||
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
|
||||
(span :class "text-sm text-stone-700" "Optimistic item B")
|
||||
(button :sx-delete "/reference/api/item/opt2"
|
||||
:sx-target "#ref-opt-item-2" :sx-swap "delete"
|
||||
:sx-optimistic "remove"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
|
||||
(p :class "text-xs text-stone-400"
|
||||
"Items fade out immediately on click (optimistic), then are removed when the server responds.")))
|
||||
|
||||
@@ -579,21 +579,19 @@ def _reference_attr_detail_sx(slug: str) -> str:
|
||||
|
||||
|
||||
async def _reference_attrs_sx() -> str:
|
||||
from content.pages import REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS, HTMX_MISSING_ATTRS
|
||||
from content.pages import REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS
|
||||
req = await _attr_table_sx("Request Attributes", REQUEST_ATTRS)
|
||||
beh = await _attr_table_sx("Behavior Attributes", BEHAVIOR_ATTRS)
|
||||
uniq = await _attr_table_sx("Unique to sx", SX_UNIQUE_ATTRS)
|
||||
missing = await _attr_table_sx("htmx features not yet in sx", HTMX_MISSING_ATTRS)
|
||||
return (
|
||||
f'(~doc-page :title "Attribute Reference"'
|
||||
f' (p :class "text-stone-600 mb-6"'
|
||||
f' "sx attributes mirror htmx where possible. This table shows what exists, '
|
||||
f'what\'s unique to sx, and what\'s not yet implemented.")'
|
||||
f' "sx attributes mirror htmx where possible. This table shows all '
|
||||
f'available attributes and their status.")'
|
||||
f' (div :class "space-y-8"'
|
||||
f' {req}'
|
||||
f' {beh}'
|
||||
f' {uniq}'
|
||||
f' {missing}))'
|
||||
f' {uniq}))'
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user