Implement 7 missing sx attributes: boost, preload, preserve, indicator, validate, ignore, optimistic
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
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:
@@ -117,20 +117,17 @@ BEHAVIOR_ATTRS = [
|
||||
|
||||
SX_UNIQUE_ATTRS = [
|
||||
("sx-retry", "Exponential backoff retry on request failure", True),
|
||||
("sx-boost", "Progressively enhance all links and forms in a container with AJAX navigation", True),
|
||||
("sx-preload", "Preload content on hover/focus for instant response on click", True),
|
||||
("sx-preserve", "Preserve element across swaps — keeps DOM state, event listeners, and scroll position", True),
|
||||
("sx-indicator", "CSS selector for a loading indicator element to show/hide during requests", True),
|
||||
("sx-validate", "Run browser constraint validation (or custom validator) before sending the request", True),
|
||||
("sx-ignore", "Ignore element and its subtree during morph/swap — no updates applied", True),
|
||||
("sx-optimistic", "Apply optimistic UI updates immediately, reconcile on server response", True),
|
||||
("data-sx", "Client-side rendering — evaluate the sx source in this attribute and render into the element", True),
|
||||
("data-sx-env", "Provide environment variables as JSON for data-sx rendering", True),
|
||||
]
|
||||
|
||||
HTMX_MISSING_ATTRS = [
|
||||
("hx-boost", "Progressively enhance links and forms (not yet implemented)", False),
|
||||
("hx-preload", "Preload content on hover/focus (not yet implemented)", False),
|
||||
("hx-preserve", "Preserve element across swaps (not yet implemented)", False),
|
||||
("hx-optimistic", "Optimistic UI updates (not yet implemented)", False),
|
||||
("hx-indicator", "sx uses .sx-request CSS class instead — no dedicated attribute (not yet implemented)", False),
|
||||
("hx-validate", "Custom validation (not yet implemented — sx has sx-disable)", False),
|
||||
("hx-ignore", "Ignore element (not yet implemented — sx has sx-disable)", False),
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reference: Headers
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -719,4 +716,143 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
' :data-sx-env \'{"title": "Dynamic", "message": "From env"}\')'
|
||||
),
|
||||
},
|
||||
|
||||
# --- New attributes ---
|
||||
"sx-boost": {
|
||||
"description": (
|
||||
"Progressively enhance all descendant links and forms with AJAX navigation. "
|
||||
"Links become sx-get requests with pushState, forms become sx-post/sx-get requests. "
|
||||
"No explicit sx-* attributes needed on each link or form — just place sx-boost on a container."
|
||||
),
|
||||
"demo": "ref-boost-demo",
|
||||
"example": (
|
||||
'(nav :sx-boost "true"\n'
|
||||
' (a :href "/docs/introduction" "Introduction")\n'
|
||||
' (a :href "/docs/components" "Components")\n'
|
||||
' (a :href "/docs/evaluator" "Evaluator"))'
|
||||
),
|
||||
},
|
||||
"sx-preload": {
|
||||
"description": (
|
||||
"Preload the response in the background when the user hovers over or focuses "
|
||||
"an element with sx-get. When they click, the cached response is used instantly "
|
||||
"instead of making a new request. Cache entries expire after 30 seconds. "
|
||||
'Values: "mousedown" (default, preloads on mousedown) or '
|
||||
'"mouseover" (preloads earlier on hover with 100ms debounce).'
|
||||
),
|
||||
"demo": "ref-preload-demo",
|
||||
"example": (
|
||||
'(button :sx-get "/reference/api/time"\n'
|
||||
' :sx-target "#ref-preload-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
' :sx-preload "mouseover"\n'
|
||||
' "Hover then click (preloaded)")'
|
||||
),
|
||||
"handler": (
|
||||
'(defhandler ref-preload-time (&key)\n'
|
||||
' (let ((now (format-time (now) "%H:%M:%S.%f")))\n'
|
||||
' (span :class "text-stone-800 text-sm"\n'
|
||||
' "Preloaded at: " (strong now))))'
|
||||
),
|
||||
},
|
||||
"sx-preserve": {
|
||||
"description": (
|
||||
"Preserve an element across morph/swap operations. The element must have an id. "
|
||||
"During morphing, the element is kept in place with its full DOM state intact — "
|
||||
"event listeners, scroll position, video playback, user input, and any other state "
|
||||
"are preserved. The incoming version of the element is discarded."
|
||||
),
|
||||
"demo": "ref-preserve-demo",
|
||||
"example": (
|
||||
'(div :id "my-player" :sx-preserve "true"\n'
|
||||
' (video :src "/media/clip.mp4" :controls "true"\n'
|
||||
' "Video playback is preserved across swaps."))'
|
||||
),
|
||||
},
|
||||
"sx-indicator": {
|
||||
"description": (
|
||||
"Specifies a CSS selector for a loading indicator element. "
|
||||
"The indicator receives the .sx-request class during the request, "
|
||||
"and the class is removed when the request completes (success or error). "
|
||||
"Use CSS to show/hide the indicator based on the .sx-request class."
|
||||
),
|
||||
"demo": "ref-indicator-demo",
|
||||
"example": (
|
||||
'(button :sx-get "/reference/api/slow-echo"\n'
|
||||
' :sx-target "#ref-indicator-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
' :sx-indicator "#ref-spinner"\n'
|
||||
' "Load (slow)")\n'
|
||||
'\n'
|
||||
'(span :id "ref-spinner"\n'
|
||||
' :class "hidden sx-request:inline text-violet-600 text-sm"\n'
|
||||
' "Loading...")'
|
||||
),
|
||||
"handler": (
|
||||
'(defhandler ref-indicator-slow (&key)\n'
|
||||
' (sleep 1500)\n'
|
||||
' (let ((now (format-time (now) "%H:%M:%S")))\n'
|
||||
' (span "Loaded at " (strong now))))'
|
||||
),
|
||||
},
|
||||
"sx-validate": {
|
||||
"description": (
|
||||
"Run browser constraint validation before sending the request. "
|
||||
"If validation fails, the request is not sent and an sx:validationFailed "
|
||||
"event is dispatched. Works with standard HTML5 validation attributes "
|
||||
'(required, pattern, minlength, etc). Set to "true" for built-in validation, '
|
||||
"or provide a function name for custom validation."
|
||||
),
|
||||
"demo": "ref-validate-demo",
|
||||
"example": (
|
||||
'(form :sx-post "/reference/api/greet"\n'
|
||||
' :sx-target "#ref-validate-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
' :sx-validate "true"\n'
|
||||
' (input :type "email" :name "email"\n'
|
||||
' :required "true"\n'
|
||||
' :placeholder "Enter email (required)")\n'
|
||||
' (button :type "submit" "Submit"))'
|
||||
),
|
||||
"handler": (
|
||||
'(defhandler ref-validate-greet (&key)\n'
|
||||
' (let ((email (or (form-data "email") "none")))\n'
|
||||
' (span "Validated: " (strong email))))'
|
||||
),
|
||||
},
|
||||
"sx-ignore": {
|
||||
"description": (
|
||||
"During morph/swap, this element and its subtree are completely skipped — "
|
||||
"no attribute updates, no child reconciliation, no removal. "
|
||||
"Unlike sx-preserve (which requires an id and preserves by identity), "
|
||||
"sx-ignore works positionally and means 'don\\'t touch this subtree at all.'"
|
||||
),
|
||||
"demo": "ref-ignore-demo",
|
||||
"example": (
|
||||
'(div :sx-ignore "true"\n'
|
||||
' (p "This content is never updated by morph/swap.")\n'
|
||||
' (input :type "text" :placeholder "Type here — preserved"))'
|
||||
),
|
||||
},
|
||||
"sx-optimistic": {
|
||||
"description": (
|
||||
"Apply a client-side preview of the expected result immediately, "
|
||||
"then reconcile when the server responds. On error, the original state "
|
||||
'is restored. Values: "remove" (hide the target), '
|
||||
'"add-class:<name>" (add a CSS class), "disable" (disable the element).'
|
||||
),
|
||||
"demo": "ref-optimistic-demo",
|
||||
"example": (
|
||||
'(button :sx-delete "/reference/api/item/opt1"\n'
|
||||
' :sx-target "#ref-opt-item"\n'
|
||||
' :sx-swap "delete"\n'
|
||||
' :sx-optimistic "remove"\n'
|
||||
' "Delete (optimistic)")'
|
||||
),
|
||||
"handler": (
|
||||
'(defhandler ref-optimistic-delete (&key)\n'
|
||||
' (sleep 800)\n'
|
||||
' "")'
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user