Hyperscript: hide/show strategy (opacity/visibility), add/remove query-all

Parser: hide/show handle `with opacity/visibility/display` strategy
and properly detect target vs command boundaries. Compiler: emit
correct CSS property per strategy, add-class/remove-class use
for-each+query-all for class selectors. Runtime: hs-query-all uses
dom-body, hs-each helper for collection iteration.

Generator: inline run().toEqual() pattern for eval-only tests.

372/831 (45%)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-12 12:07:06 +00:00
parent 854ed9c027
commit ae32254dfb
9 changed files with 515 additions and 459 deletions

View File

@@ -751,10 +751,21 @@
(list (quote hs-last) (hs-to-sx (nth ast 2)) (nth ast 1))
(list (quote hs-query-last) (nth ast 1))))
((= head (quote add-class))
(list
(quote dom-add-class)
(hs-to-sx (nth ast 2))
(nth ast 1)))
(let
((raw-tgt (nth ast 2)))
(if
(and (list? raw-tgt) (= (first raw-tgt) (quote query)))
(list
(quote for-each)
(list
(quote fn)
(list (quote _el))
(list (quote dom-add-class) (quote _el) (nth ast 1)))
(list (quote hs-query-all) (nth raw-tgt 1)))
(list
(quote dom-add-class)
(hs-to-sx raw-tgt)
(nth ast 1)))))
((= head (quote multi-add-class))
(let
((target (hs-to-sx (nth ast 1)))
@@ -774,10 +785,24 @@
(fn (cls) (list (quote dom-remove-class) target cls))
classes))))
((= head (quote remove-class))
(list
(quote dom-remove-class)
(hs-to-sx (nth ast 2))
(nth ast 1)))
(let
((raw-tgt (nth ast 2)))
(if
(and (list? raw-tgt) (= (first raw-tgt) (quote query)))
(list
(quote for-each)
(list
(quote fn)
(list (quote _el))
(list
(quote dom-remove-class)
(quote _el)
(nth ast 1)))
(list (quote hs-query-all) (nth raw-tgt 1)))
(list
(quote dom-remove-class)
(hs-to-sx raw-tgt)
(nth ast 1)))))
((= head (quote toggle-class))
(list
(quote hs-toggle-class!)

View File

@@ -13,45 +13,53 @@
;; 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)))
hs-each
(fn
(target action)
(if (list? target) (for-each action target) (action target))))
;; Register for every occurrence (no queuing — each fires independently).
;; Stock hyperscript queues by default; "every" disables queuing.
(define
hs-on-every
hs-on
(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)))
(define
hs-on-every
(fn (target event-name handler) (dom-listen target event-name handler)))
;; ── 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))))
(define hs-init (fn (thunk) (thunk)))
;; Wait for a DOM event on a target.
;; (hs-wait-for target event-name) — suspends until event fires
(define hs-wait (fn (ms) (perform (list (quote io-sleep) ms))))
;; Wait for CSS transitions/animations to settle on an element.
(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-settle (fn (target) (perform (list (quote io-settle) target))))
;; Toggle between two classes — exactly one is active at a time.
(define
hs-toggle-class!
(fn (target cls) (host-call (host-get target "classList") "toggle" cls)))
;; Toggle between two classes — exactly one is active at a time.
;; Take a class from siblings — add to target, remove from others.
;; (hs-take! target cls) — like radio button class behavior
(define
hs-toggle-between!
(fn
@@ -61,8 +69,10 @@
(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
;; ── DOM insertion ───────────────────────────────────────────────
;; Put content at a position relative to a target.
;; pos: "into" | "before" | "after"
(define
hs-toggle-style!
(fn
@@ -86,10 +96,9 @@
(dom-set-style target prop "hidden")
(dom-set-style target prop "")))))))
;; ── DOM insertion ───────────────────────────────────────────────
;; ── Navigation / traversal ──────────────────────────────────────
;; Put content at a position relative to a target.
;; pos: "into" | "before" | "after"
;; Navigate to a URL.
(define
hs-take!
(fn
@@ -105,9 +114,7 @@
(for-each (fn (el) (dom-remove-attr el name)) els)
(dom-set-attr target name "true"))))))
;; ── Navigation / traversal ──────────────────────────────────────
;; Navigate to a URL.
;; Find next sibling matching a selector (or any sibling).
(define
hs-put!
(fn
@@ -120,10 +127,10 @@
((= pos "start") (dom-insert-adjacent-html target "afterbegin" value))
((= pos "end") (dom-insert-adjacent-html target "beforeend" value)))))
;; Find next sibling matching a selector (or any sibling).
;; Find previous sibling matching a selector.
(define hs-navigate! (fn (url) (perform (list (quote io-navigate) url))))
;; Find previous sibling matching a selector.
;; First element matching selector within a scope.
(define
hs-scroll!
(fn
@@ -136,7 +143,7 @@
((= position "bottom") (dict :block "end"))
(true (dict :block "start")))))))
;; First element matching selector within a scope.
;; Last element matching selector.
(define
hs-halt!
(fn
@@ -146,12 +153,14 @@
(host-call event "preventDefault" (list))
(when (= mode "event") (host-call event "stopPropagation" (list))))))
;; Last element matching selector.
;; First/last within a specific scope.
(define hs-select! (fn (target) (host-call target "select" (list))))
;; First/last within a specific scope.
(define hs-reset! (fn (target) (host-call target "reset" (list))))
;; ── Iteration ───────────────────────────────────────────────────
;; Repeat a thunk N times.
(define
hs-next
(fn
@@ -171,9 +180,7 @@
(true (find-next (dom-next-sibling el))))))
(find-next sibling)))))
;; ── Iteration ───────────────────────────────────────────────────
;; Repeat a thunk N times.
;; Repeat forever (until break — relies on exception/continuation).
(define
hs-previous
(fn
@@ -193,27 +200,24 @@
(true (find-prev (dom-get-prop el "previousElementSibling"))))))
(find-prev sibling)))))
;; Repeat forever (until break — relies on exception/continuation).
(define
hs-query-all
(fn
(sel)
(dom-query-all
(host-call (host-global "document") "querySelector" (list "body"))
sel)))
;; ── Fetch ───────────────────────────────────────────────────────
;; Fetch a URL, parse response according to format.
;; (hs-fetch url format) — format is "json" | "text" | "html"
(define
hs-query-first
(fn (sel) (host-call (host-global "document") "querySelector" sel)))
(define hs-query-all (fn (sel) (dom-query-all (dom-body) sel)))
;; ── Type coercion ───────────────────────────────────────────────
;; Coerce a value to a type by name.
;; (hs-coerce value type-name) — type-name is "Int", "Float", "String", etc.
(define
hs-query-first
(fn (sel) (host-call (host-global "document") "querySelector" sel)))
;; ── Object creation ─────────────────────────────────────────────
;; Make a new object of a given type.
;; (hs-make type-name) — creates empty object/collection
(define
hs-query-last
(fn
@@ -222,17 +226,17 @@
((all (dom-query-all (dom-body) sel)))
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
;; ── Object creation ─────────────────────────────────────────────
;; Make a new object of a given type.
;; (hs-make type-name) — creates empty object/collection
(define hs-first (fn (scope sel) (dom-query-all scope sel)))
;; ── 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-first (fn (scope sel) (dom-query-all scope sel)))
;; ── 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-last
(fn
@@ -241,10 +245,10 @@
((all (dom-query-all scope sel)))
(if (> (len all) 0) (nth all (- (len all) 1)) nil))))
;; ── Measurement ─────────────────────────────────────────────────
;; ── Transition ──────────────────────────────────────────────────
;; Measure an element's bounding rect, store as local variables.
;; Returns a dict with x, y, width, height, top, left, right, bottom.
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-repeat-times
(fn
@@ -254,10 +258,6 @@
(fn (i) (when (< i n) (do (thunk) (do-repeat (+ i 1))))))
(do-repeat 0)))
;; ── Transition ──────────────────────────────────────────────────
;; Transition a CSS property to a value, optionally with duration.
;; (hs-transition target prop value duration)
(define
hs-repeat-forever
(fn
@@ -365,14 +365,14 @@
(value type-name)
(if (nil? value) false (hs-type-check value type-name))))
(define
hs-strict-eq
(fn (a b) (and (= (type-of a) (type-of b)) (= a b))))
(define
hs-falsy?
(fn
@@ -384,7 +384,8 @@
((and (list? v) (= (len v) 0)) true)
((= v 0) true)
(true false))))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
(define
hs-matches?
(fn
@@ -393,8 +394,7 @@
(string? target)
(if (= pattern ".*") true (string-contains? target pattern))
false)))
;; ── Sandbox/test runtime additions ──────────────────────────────
;; Property access — dot notation and .length
;; DOM query stub — sandbox returns empty list
(define
hs-contains?
(fn
@@ -414,7 +414,7 @@
true
(hs-contains? (rest collection) item)))))
(true false))))
;; DOM query stub — sandbox returns empty list
;; Method dispatch — obj.method(args)
(define
hs-empty?
(fn
@@ -425,13 +425,13 @@
((list? v) (= (len v) 0))
((dict? v) (= (len (keys v)) 0))
(true false))))
;; Method dispatch — obj.method(args)
(define hs-first (fn (lst) (first lst)))
;; ── 0.9.90 features ─────────────────────────────────────────────
;; beep! — debug logging, returns value unchanged
(define hs-last (fn (lst) (last lst)))
(define hs-first (fn (lst) (first lst)))
;; Property-based is — check obj.key truthiness
(define hs-last (fn (lst) (last lst)))
;; Array slicing (inclusive both ends)
(define
hs-template
(fn
@@ -517,7 +517,7 @@
(set! i (+ i 1))
(tpl-loop)))))))
(do (tpl-loop) result))))
;; Array slicing (inclusive both ends)
;; Collection: sorted by
(define
hs-make-object
(fn
@@ -529,7 +529,7 @@
(fn (pair) (dict-set! d (first pair) (nth pair 1)))
pairs)
d))))
;; Collection: sorted by
;; Collection: sorted by descending
(define
hs-method-call
(fn
@@ -552,11 +552,11 @@
(if (= (first lst) item) i (idx-loop (rest lst) (+ i 1))))))
(idx-loop obj 0)))
(true nil))))
;; Collection: sorted by descending
(define hs-beep (fn (v) v))
;; Collection: split by
(define hs-prop-is (fn (obj key) (not (hs-falsy? (host-get obj key)))))
(define hs-beep (fn (v) v))
;; Collection: joined by
(define hs-prop-is (fn (obj key) (not (hs-falsy? (host-get obj key)))))
(define
hs-slice
(fn