Step 18 (part 5): _hyperscript runtime shims — 25 functions
lib/hyperscript/runtime.sx — thin wrappers over web/lib/dom.sx primitives implementing hyperscript-specific semantics: Event handling: hs-on, hs-on-every, hs-init Async/timing: hs-wait (IO suspend), hs-wait-for, hs-settle Classes: hs-toggle-class!, hs-toggle-between!, hs-take! DOM insertion: hs-put! (into/before/after) Navigation: hs-navigate!, hs-next, hs-previous, hs-query-first/last Iteration: hs-repeat-times, hs-repeat-forever Fetch: hs-fetch (json/text/html format dispatch) Type coercion: hs-coerce (Int/Float/String/Boolean/Array) Object creation: hs-make (Object/Array/Set/Map) Behaviors: hs-install Measurement: hs-measure Transitions: hs-transition (CSS property + optional duration) 23 runtime + 7 end-to-end pipeline tests. 3099/3099 full build, zero regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
265
lib/hyperscript/runtime.sx
Normal file
265
lib/hyperscript/runtime.sx
Normal file
@@ -0,0 +1,265 @@
|
||||
;; _hyperscript runtime shims
|
||||
;;
|
||||
;; Thin wrappers over web/lib/dom.sx and web/lib/browser.sx primitives
|
||||
;; that implement hyperscript-specific semantics (async transparency,
|
||||
;; class toggling, event waiting, iteration, type coercion).
|
||||
;;
|
||||
;; These are the functions that hs-to-sx (compiler.sx) emits calls to.
|
||||
;; Each is a pure define — no platform dependency beyond the DOM/browser
|
||||
;; primitives already available in the SX web platform.
|
||||
|
||||
;; ── Event handling ──────────────────────────────────────────────
|
||||
|
||||
;; Register an event listener. Returns unlisten function.
|
||||
;; (hs-on target event-name handler) → unlisten-fn
|
||||
(define
|
||||
hs-on
|
||||
(fn (target event-name handler) (dom-listen target event-name handler)))
|
||||
|
||||
;; Register for every occurrence (no queuing — each fires independently).
|
||||
;; Stock hyperscript queues by default; "every" disables queuing.
|
||||
(define
|
||||
hs-on-every
|
||||
(fn (target event-name handler) (dom-listen target event-name handler)))
|
||||
|
||||
;; Run an initializer function immediately.
|
||||
;; (hs-init thunk) — called at element boot time
|
||||
(define hs-init (fn (thunk) (thunk)))
|
||||
|
||||
;; ── Async / timing ──────────────────────────────────────────────
|
||||
|
||||
;; Wait for a duration in milliseconds.
|
||||
;; In hyperscript, wait is async-transparent — execution pauses.
|
||||
;; Here we use perform/IO suspension for true pause semantics.
|
||||
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
|
||||
|
||||
;; Wait for a DOM event on a target.
|
||||
;; (hs-wait-for target event-name) — suspends until event fires
|
||||
(define
|
||||
hs-wait-for
|
||||
(fn
|
||||
(target event-name)
|
||||
(perform (list (quote io-wait-event) target event-name))))
|
||||
|
||||
;; Wait for CSS transitions/animations to settle on an element.
|
||||
(define hs-settle (fn (target) (perform (list (quote io-settle) target))))
|
||||
|
||||
;; ── Class manipulation ──────────────────────────────────────────
|
||||
|
||||
;; Toggle a single class on an element.
|
||||
(define
|
||||
hs-toggle-class!
|
||||
(fn
|
||||
(target cls)
|
||||
(if
|
||||
(dom-has-class? target cls)
|
||||
(dom-remove-class target cls)
|
||||
(dom-add-class target cls))))
|
||||
|
||||
;; Toggle between two classes — exactly one is active at a time.
|
||||
(define
|
||||
hs-toggle-between!
|
||||
(fn
|
||||
(target cls1 cls2)
|
||||
(if
|
||||
(dom-has-class? target cls1)
|
||||
(do (dom-remove-class target cls1) (dom-add-class target cls2))
|
||||
(do (dom-remove-class target cls2) (dom-add-class target cls1)))))
|
||||
|
||||
;; Take a class from siblings — add to target, remove from others.
|
||||
;; (hs-take! target cls) — like radio button class behavior
|
||||
(define
|
||||
hs-take!
|
||||
(fn
|
||||
(target cls)
|
||||
(let
|
||||
((parent (dom-parent target)))
|
||||
(when
|
||||
parent
|
||||
(for-each
|
||||
(fn (child) (dom-remove-class child cls))
|
||||
(dom-child-list parent)))
|
||||
(dom-add-class target cls))))
|
||||
|
||||
;; ── DOM insertion ───────────────────────────────────────────────
|
||||
|
||||
;; Put content at a position relative to a target.
|
||||
;; pos: "into" | "before" | "after"
|
||||
(define
|
||||
hs-put!
|
||||
(fn
|
||||
(value pos target)
|
||||
(cond
|
||||
((= pos "into") (dom-set-inner-html target value))
|
||||
((= pos "before")
|
||||
(dom-insert-adjacent-html target "beforebegin" value))
|
||||
((= pos "after") (dom-insert-adjacent-html target "afterend" value)))))
|
||||
|
||||
;; ── Navigation / traversal ──────────────────────────────────────
|
||||
|
||||
;; Navigate to a URL.
|
||||
(define hs-navigate! (fn (url) (perform (list (quote io-navigate) url))))
|
||||
|
||||
;; Find next sibling matching a selector (or any sibling).
|
||||
(define
|
||||
hs-next
|
||||
(fn
|
||||
(target sel)
|
||||
(if
|
||||
(= sel "*")
|
||||
(dom-next-sibling target)
|
||||
(let
|
||||
((sibling (dom-next-sibling target)))
|
||||
(define
|
||||
find-next
|
||||
(fn
|
||||
(el)
|
||||
(cond
|
||||
((nil? el) nil)
|
||||
((dom-matches? el sel) el)
|
||||
(true (find-next (dom-next-sibling el))))))
|
||||
(find-next sibling)))))
|
||||
|
||||
;; Find previous sibling matching a selector.
|
||||
(define
|
||||
hs-previous
|
||||
(fn
|
||||
(target sel)
|
||||
(if
|
||||
(= sel "*")
|
||||
(dom-get-prop target "previousElementSibling")
|
||||
(let
|
||||
((sibling (dom-get-prop target "previousElementSibling")))
|
||||
(define
|
||||
find-prev
|
||||
(fn
|
||||
(el)
|
||||
(cond
|
||||
((nil? el) nil)
|
||||
((dom-matches? el sel) el)
|
||||
(true (find-prev (dom-get-prop el "previousElementSibling"))))))
|
||||
(find-prev sibling)))))
|
||||
|
||||
;; First element matching selector within a scope.
|
||||
(define hs-query-first (fn (sel) (dom-query sel)))
|
||||
|
||||
;; Last element matching selector.
|
||||
(define
|
||||
hs-query-last
|
||||
(fn
|
||||
(sel)
|
||||
(let
|
||||
((all (dom-query-all (dom-body) sel)))
|
||||
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
|
||||
|
||||
;; First/last within a specific scope.
|
||||
(define hs-first (fn (scope sel) (dom-query-all scope sel)))
|
||||
|
||||
(define
|
||||
hs-last
|
||||
(fn
|
||||
(scope sel)
|
||||
(let
|
||||
((all (dom-query-all scope sel)))
|
||||
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
|
||||
|
||||
;; ── Iteration ───────────────────────────────────────────────────
|
||||
|
||||
;; Repeat a thunk N times.
|
||||
(define
|
||||
hs-repeat-times
|
||||
(fn
|
||||
(n thunk)
|
||||
(define
|
||||
do-repeat
|
||||
(fn (i) (when (< i n) (thunk) (do-repeat (+ i 1)))))
|
||||
(do-repeat 0)))
|
||||
|
||||
;; Repeat forever (until break — relies on exception/continuation).
|
||||
(define
|
||||
hs-repeat-forever
|
||||
(fn
|
||||
(thunk)
|
||||
(define do-forever (fn () (thunk) (do-forever)))
|
||||
(do-forever)))
|
||||
|
||||
;; ── Fetch ───────────────────────────────────────────────────────
|
||||
|
||||
;; Fetch a URL, parse response according to format.
|
||||
;; (hs-fetch url format) — format is "json" | "text" | "html"
|
||||
(define
|
||||
hs-fetch
|
||||
(fn
|
||||
(url format)
|
||||
(let
|
||||
((response (perform (list (quote io-fetch) url))))
|
||||
(cond
|
||||
((= format "json") (perform (list (quote io-parse-json) response)))
|
||||
((= format "text") (perform (list (quote io-parse-text) response)))
|
||||
((= format "html") (perform (list (quote io-parse-html) response)))
|
||||
(true response)))))
|
||||
|
||||
;; ── Type coercion ───────────────────────────────────────────────
|
||||
|
||||
;; Coerce a value to a type by name.
|
||||
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
|
||||
(define
|
||||
hs-coerce
|
||||
(fn
|
||||
(value type-name)
|
||||
(cond
|
||||
((= type-name "Int") (+ value 0))
|
||||
((= type-name "Integer") (+ value 0))
|
||||
((= type-name "Float") (+ value 0))
|
||||
((= type-name "Number") (+ value 0))
|
||||
((= type-name "String") (str value))
|
||||
((= type-name "Boolean") (if value true false))
|
||||
((= type-name "Array") (if (list? value) value (list value)))
|
||||
(true value))))
|
||||
|
||||
;; ── Object creation ─────────────────────────────────────────────
|
||||
|
||||
;; Make a new object of a given type.
|
||||
;; (hs-make type-name) — creates empty object/collection
|
||||
(define
|
||||
hs-make
|
||||
(fn
|
||||
(type-name)
|
||||
(cond
|
||||
((= type-name "Object") (dict))
|
||||
((= type-name "Array") (list))
|
||||
((= type-name "Set") (list))
|
||||
((= type-name "Map") (dict))
|
||||
(true (dict)))))
|
||||
|
||||
;; ── Behavior installation ───────────────────────────────────────
|
||||
|
||||
;; Install a behavior on an element.
|
||||
;; A behavior is a function that takes (me ...params) and sets up features.
|
||||
;; (hs-install behavior-fn me ...args)
|
||||
(define hs-install (fn (behavior-fn) (behavior-fn me)))
|
||||
|
||||
;; ── Measurement ─────────────────────────────────────────────────
|
||||
|
||||
;; Measure an element's bounding rect, store as local variables.
|
||||
;; Returns a dict with x, y, width, height, top, left, right, bottom.
|
||||
(define
|
||||
hs-measure
|
||||
(fn (target) (perform (list (quote io-measure) target))))
|
||||
|
||||
;; ── Transition ──────────────────────────────────────────────────
|
||||
|
||||
;; Transition a CSS property to a value, optionally with duration.
|
||||
;; (hs-transition target prop value duration)
|
||||
(define
|
||||
hs-transition
|
||||
(fn
|
||||
(target prop value duration)
|
||||
(when
|
||||
duration
|
||||
(dom-set-style
|
||||
target
|
||||
"transition"
|
||||
(str prop " " (/ duration 1000) "s")))
|
||||
(dom-set-style target prop value)
|
||||
(when duration (hs-settle target))))
|
||||
Reference in New Issue
Block a user